ReactNativeCookbook
SecondEdition





Step-by-steprecipesforsolvingcommonReactNativedevelopmentproblems







DanWard







BIRMINGHAM-MUMBAI
ReactNativeCookbookSecond
Edition

Copyright©2019PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,
withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,the
informationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishingorits
dealersanddistributors,willbeheldliableforanydamagescausedorallegedtohavebeencauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookby
theappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
CommissioningEditor:AmarabhabBanerjee
AcquisitionEditor:TrushaShriyan
ContentDevelopmentEditor:ArunNadar
TechnicalEditor:LeenaPatil
CopyEditor:SafisEditing
ProjectCoordinator:KinjalBari
Proofreader:SafisEditing
Indexer:TejalDaruwaleSoni
Graphics:AlishonMendonsa
ProductionCoordinator:ArvindkumarGupta

Firstpublished:December2016
Secondedition:January2019
Productionreference:1310119

PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
Birmingham
B32PB,UK.
ISBN978-1-78899-192-6
www.packtpub.com

mapt.io
Maptisanonlinedigitallibrarythatgivesyoufullaccesstoover5,000books
andvideos,aswellasindustryleadingtoolstohelpyouplanyourpersonal
developmentandadvanceyourcareer.Formoreinformation,pleasevisitour
website.
Whysubscribe?
SpendlesstimelearningandmoretimecodingwithpracticaleBooksand
Videosfromover4,000industryprofessionals
ImproveyourlearningwithSkillPlansbuiltespeciallyforyou
GetafreeeBookorvideoeverymonth
Maptisfullysearchable
Copyandpaste,print,andbookmarkcontent
Packt.com
DidyouknowthatPacktofferseBookversionsofeverybookpublished,with
PDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.packt.
comandasaprintbookcustomer,youareentitledtoadiscountontheeBook
copy.Getintouchwithusatcustomercare@packtpub.comformoredetails.
Atwww.packt.com,youcanalsoreadacollectionoffreetechnicalarticles,signup
forarangeoffreenewsletters,andreceiveexclusivediscountsandofferson
PacktbooksandeBooks.
Contributors
Abouttheauthor
DanWardisafull-stackdeveloperandwebtechnologyconsultantwhohasa
numberofyearsofexperienceworkingonmobileapplicationswithReact
Native,anddevelopingwebapplicationswithReact,Vue,andAngular.He'salso
aco-founderatgitconnected,andco-editorattheassociatedMedium
publication.HisprofessionalinterestsincludeReactNativedevelopment,
modernwebdevelopment,andtechnicalwriting.HealsohasaBAinEnglish
LiteraturefromFloridaStateUniversity.
Aboutthereviewer
AshokKumarShasbeenworkinginthemobiledevelopmentdomainforabout
sixyears.Inhisearlydays,hewasaJavaScriptandNode.jsdeveloper.Thanks
tohisstrongwebdevelopmentskills,hemasteredwebandmobiledevelopment.
HeisaGoogle-certifiedengineer,aspeakeratglobal-scaleconferences,
includingDroidConBerlinandMODS,andalsorunsaYouTubechannelcalled
AndroidABCDforAndroiddevelopers.Healsocontributestoopensource
heavilywithaviewtoimprovinghise-karma.HehaswrittenbooksonWearOS
programmingandMasteringFirebaseToolchain.Hehasalsoreviewedbookson
mobileandwebdevelopment,namely,MasteringJUnit5,AndroidProgramming
forBeginners,andBuildingEnterpriseJavaScriptApplications.
Iwouldliketothankmyfamily,mostlymymother,forherinfinitesupportineverypossibleway,aswellas
familymembersShylaja,Sumitra,Krishna,andVinisha,andmyfiancee,GeethaShree.


Packtissearchingforauthorslike
you
Ifyou'reinterestedinbecominganauthorforPackt,pleasevisitauthors.packtpub.c
omandapplytoday.Wehaveworkedwiththousandsofdevelopersandtech
professionals,justlikeyou,tohelpthemsharetheirinsightwiththeglobaltech
community.Youcanmakeageneralapplication,applyforaspecifichottopic
thatwearerecruitinganauthorfor,orsubmityourownidea.
TableofContents
TitlePage
CopyrightandCredits
ReactNativeCookbookSecondEdition
AboutPackt
Whysubscribe?
Packt.com
Contributors
Abouttheauthor
Aboutthereviewer
Packtissearchingforauthorslikeyou
Preface
Whothisbookisfor
Whatthisbookcovers
Togetthemostoutofthisbook
Downloadtheexamplecodefiles
Downloadthecolorimages
Conventionsused
Sections
Gettingready
Howtodoit…
Howitworks…
There'smore…
Seealso
Getintouch
Reviews
1. SettingUpYourEnvironment
Technicalrequirements
Installingdependencies
InstallingXcode
InstallingGenymotion
InstallingNode.js
InstallingExpo
InstallingWatchman
Initializingyourfirstapp
Runningyourappinasimulator/emulator
RunningyourapponaniOSsimulator
RunningyourapponanAndroidemulator
Runningyourapponarealdevice
RunningyourapponaniPhoneorAndroid
Summary
Furtherreading
2. CreatingaSimpleReactNativeApp
Addingstylestoelements
Gettingready
Howtodoit...
Howitworks...
There'smore...
Usingimagestomimicavideoplayer
Gettingready
Howtodoit...
Howitworks...
Creatingatogglebutton
Gettingready
Howtodoit...
Howitworks...
There'smore...
Displayingalistofitems
Gettingready
Howtodoit...
Howitworks...
There'smore...
Usingflexboxtocreatealayout
Gettingready
Howtodoit...
Howitworks...
There'smore...
Seealso
Settingupandusingnavigation
Gettingready
Howtodoit...
Howitworks...
Seealso
3. ImplementingComplexUserInterfaces-PartI
Creatingareusablebuttonwiththemesupport
Gettingready
Howtodoit...
Howitworks...
Buildingacomplexlayoutfortabletsusingflexbox
Gettingready
Howtodoit...
There'smore...
Seealso
Includingcustomfonts
Gettingready
Howtodoit...
Howitworks...
Seealso
Usingfonticons
Gettingready
Howtodoit...
Howitworks...
Seealso
4. ImplementingComplexUserInterfaces-PartII
Dealingwithuniversalapplications
Gettingready
Howtodoit...
Howitworks...
Seealso
Detectingorientationchanges
Gettingready
Howtodoit...
There'smore...
UsingaWebViewtoembedexternalwebsites
Gettingready
Howtodoit...
Howitworks...
Linkingtowebsitesandotherapplications
Gettingready
Howtodoit...
Howitworks...
Seealso
Creatingaformcomponent
Gettingready
Howtodoit...
Howitworks...
5. ImplementingComplexUserInterfaces-PartIII
Introduction
CreatingamapappwithGoogleMaps
Gettingready
Howtodoit...
Howitworks...
There'smore...
Creatinganaudioplayer
Gettingready
Howtodoit...
Howitworks...
There'smore...
Creatinganimagecarousel
Gettingready
Howtodoit...
Howitworks...
There'smore...
Addingpushnotificationstoyourapp
Gettingready
Howtodoit...
Howitworks...
There'smore...
Implementingbrowser-basedauthentication
Gettingready
Howtodoit...
Howitworks...
Seealso
6. AddingBasicAnimationstoYourApp
Introduction
Creatingsimpleanimations
Gettingready
Howtodoit...
Howitworks...
Runningmultipleanimations
Gettingready
Howtodoit...
Howitworks...
Creatinganimatednotifications
Gettingready
Howtodoit...
Howitworks...
There'smore...
Expandingandcollapsingcontainers
Gettingready
Howtodoit...
Howitworks...
Seealso
Creatingabuttonwithaloadinganimation
Gettingready
Howtodoit...
Howitworks...
Conclusion
7. AddingAdvancedAnimationstoYourApp
Introduction
Removingitemsfromalistcomponent
Gettingready
Howtodoit...
Howitworks...
Seealso
CreatingaFacebookreactionswidget
Gettingready
Howtodoit...
Howitworks...
Displayingimagesinfullscreen
Gettingready
Howtodoit...
Howitworks...
Seealso
8. WorkingwithApplicationLogicandData
Introduction
Storingandretrievingdatalocally
Gettingready
Howtodoit...
Howitworks...
Seealso
RetrievingdatafromaremoteAPI
Gettingready
Howtodoit...
Howitworks...
SendingdatatoaremoteAPI
Gettingready
Howtodoit...
Howitworks...
Establishingreal-timecommunicationwithWebSockets
Gettingready
Howtodoit...
Howitworks...
There'smore...
IntegratingpersistentdatabasefunctionalitywithRealm
Gettingready
Howtodoit...
Howitworks...
Maskingtheapplicationuponnetworkconnectionloss
Gettingready
Howtodoit...
Howitworks...
SynchronizinglocallypersisteddatawitharemoteAPI
Gettingready
Howtodoit...
Howitworks...
LogginginwithFacebook
Gettingready
Howtodoit...
Howitworks...
9. ImplementingRedux
Introduction
InstallingReduxandpreparingourproject
Gettingstarted
Howtodoit...
Howitworks...
Definingactions
Gettingready
Howtodoit...
Howitworks...
There'smore...
Definingreducers
Gettingready
Howtodoit...
Howitworks...
SettinguptheReduxstore
Howtodoit...
Howitworks...
CommunicatingwitharemoteAPI
Gettingready
Howtodoit...
Howitworks...
Connectingthestoretotheview
Gettingready
Howtodoit...
Howitworks...
StoringofflinecontentusingRedux
Gettingready
Howtodoit...
Howitworks...
10. AppWorkflowandThird-PartyPlugins
How thischapterworks
ReactNativedevelopmenttools
Expo
ReactNativeCLI
CocoaPods
Planningyourappandchoosingyourworkflow
Howtodoit...
ExpoCLIsetup
UsingNativeBaseforcross-platformUIcomponents
Gettingready
UsingapureReactNativeapp(ReactNativeCLI)
UsinganExpoapp
Howtodoit... 
Howitworks...
Using glamorous-nativeforstylingUIcomponents
Gettingready
Howtodoit... 
Howitworks...
Usingreact-native-spinkitforaddinganimatedloadingindicators
Gettingstarted
Howtodoit...
Howitworks...
There'smore...
Usingreact-native-side-menuforaddingsidenavigationmenus
Gettingready
Howto doit...
Howitworks...
Usingreact-native-modalboxforaddingmodals
Gettingready
Howto doit...
Howitworks...
11. AddingNativeFunctionality-PartI
Introduction
ExposingcustomiOSmodules
Gettingready
Howtodoit...
Howitworks...
There'smore...
Seealso
RenderingcustomiOSviewcomponents
Howtodoit...
Howitworks...
ExposingcustomAndroidmodules
Gettingready
Howtodoit...
Howitworks...
RenderingcustomAndroidviewcomponents
Howtodoit...
Howitworks...
HandlingtheAndroidbackbutton
Gettingready
Howtodoit...
Howitworks...
12. AddingNativeFunctionality-PartII
Introduction
Reactingtochangesinapplicationstate
Howtodoit...
Howitworks...
Copyingandpastingcontent
Gettingready
Howtodoit...
Howitworks...
Receivingpushnotifications
Gettingready
Howtodoit...
Howitworks...
AuthenticatingviatouchIDorfingerprintsensor
Gettingready
Howtodoit...
Howitworks...
Hidingapplicationcontentwhenmultitasking
Gettingready
Howtodoit...
Howitworks...
BackgroundprocessingoniOS
Gettingready
Howtodoit...
Howitworks...
BackgroundprocessingonAndroid
Gettingready
Howtodoit...
Howitworks...
PlayingaudiofilesoniOS
Gettingready
Howtodoit...
Howitworks...
PlayingaudiofilesonAndroid
Gettingready
Howtodoit...
Howitworks...
13. IntegrationwithNativeApplications
Introduction
CombiningaReactNativeappandaNativeiOSapp
Gettingready
Howtodoit...
Howitworks...
Seealso
CommunicatingfromaniOSapptoReactNative
Gettingready
Howtodoit...
CommunicatingfromReactNativetoaniOSappcontainer
Gettingready
Howtodoit...
Howitworks...
HandlingbeinginvokedbyanexternaliOSapp
Gettingready
Howtodoit...
Howitworks...
EmbeddingaReactNativeappinsideaNativeAndroidapp
Gettingready
Howtodoit...
Howitworks...
CommunicatingfromanAndroidapptoReactNative
Gettingready
Howtodoit...
Howitworks...
CommunicatingfromReactNativetoanAndroidappcontainer
Gettingready
Howtodoit...
Howitworks...
HandlingbeinginvokedbyanexternalAndroidapp
Howtodoit...
Howitworks...
14. DeployingYourApp
Introduction
DeployingdevelopmentbuildstoaniOSdevice
Gettingready
Howtodoit...
Howitworks...
DeployingdevelopmentbuildstoanAndroiddevice
Gettingready
Howtodoit...
There'smore...
Howitworks...
DeployingtestbuildstoHockeyApp
Gettingready
Howtodoit...
Howitworks...
DeployingiOStestbuildstoTestFlight
Gettingready
Howtodoit...
Howitworks...
DeployingproductionbuildstotheAppleAppStore
Gettingready
Howtodoit...
Howitworks...
DeployingproductionbuildstoGooglePlayStore
Gettingready
Howtodoit...
Howitworks...
DeployingOver-The-Airupdates
Gettingready
Howtodoit...
Howitworks...
OptimizingReactNativeappsize
Gettingready
Howtodoit...
Howitworks...
15. OptimizingthePerformanceofYourApp
Introduction
OptimizingourJavaScriptcode
Gettingready
Howtodoit...
Howitworks...
OptimizingtheperformanceofcustomUIcomponents
Gettingready
Howtodoit...
Howitworks...
Seealso
Keepinganimationsrunningat60FPS
Gettingready
Howtodoit...
Howitworks
There'smore...
GettingthemostoutofListView
Gettingready
Howtodoit...
Howitworks...
Seealso
Boostingtheperformanceofourapp
Howtodoit...
Howitworks...
OptimizingtheperformanceofnativeiOSmodules
Gettingready
Howtodoit...
Howitworks...
OptimizingtheperformanceofnativeAndroidmodules
Gettingready
Howtodoit...
Howitworks...
OptimizingtheperformanceofnativeiOSUIcomponents
Gettingready
Howtodoit...
Howitworks...
OptimizingtheperformanceofnativeAndroidUIcomponents
Gettingready
Howtodoit...
Howitworks...
OtherBooksYouMayEnjoy
Leaveareview-letotherreadersknowwhatyouthink
Preface
TherearemanywaysforadevelopertobuildanappforiOSorAndroid.React
Nativestandsoutasoneofthemoststable,performant,anddeveloper-friendly
optionsforbuildinghybridmobileapps.DevelopingmobileappswithReact
NativeallowsdeveloperstobuildiOSandAndroidappsinasinglecodebase,
withtheaddedabilityforcode-sharingbetweenthetwoplatforms.
Evenbetter,adeveloperwithexperienceofbuildingwebappsinReactwillbe
aheadofthegame,sincemanyofthesamepatternsandconventionsarecarried
overintoReactNative.Ifyou'vehadexperienceofbuildingwebappswith
React,oranotherframeworkbasedonModel,View,Component(MVC),you'll
feelrightathomebuildingmobileappsinReactNative.
Thisbookisintendedtoserveasago-toreferenceforsolutionstocommon
problemsyou'lllikelyfacewhenbuildingawidevarietyofapps.Eachchapteris
presentedasaseriesofstep-by-steprecipesthateachexplainhowtobuilda
singlefeatureofanoverallapp.
ReactNativeisanevolvinglanguage.Atthetimeofwriting,it'sstillinthe0.5x
stageofthedevelopmentlifecycle,sotherearesomethingsthatwillchangein
themonthsandyearstocome.Bestpracticescouldmorphintostaleideas,orthe
opensourcepackageshighlightedherecouldfalloutoffavor.I'vedoneallI
couldtokeepthistextasuptodateaspossible,buttechnologymovesfast,soit's
impossibleforabooktokeepupbyitself.Therepositoryforofallthecode
coveredinthisbookishostedonGitHubat.Ifyoufindanythinginthecode
herethatdoesn'tseemtobeworkingcorrectly,youcansubmitanissue.Or,if
you'vegotabetterwaytodosomething,considersubmittingapullrequest!
IhopeyoufindthisbookhelpfulonyourwaythroughthelandofReactNative.
Happydeveloping!
Whothisbookisfor
ThisbookhasbeendesignedwithbeginnerReactNativedevelopersinmind.
Evenifyoudon'thavealotofexperiencewithwebdevelopment,theJavaScript
foundinthisbookshouldhopefullyneverbeoveryourhead.I'vetriedtoavoid
complexitywhereverpossible,tokeepthefocusonthelessonbeingtaught
withinagivenrecipe.
ThisbookalsoassumesthedeveloperworksonacomputerrunningmacOS.
WhileitistechnicallypossibletodevelopReactNativeappsusingWindowsor
Linux,thereareanumberoflimitationsthatmakemacOSmachinesmuchmore
preferableforReactNativedevelopment,includingtheabilitiestoworkwith
nativeiOScodeviaXcode,runiOScodeontheiOSsimulator,andworkwith
themostrobustdevelopmenttoolsforReactNativeappdevelopment.
Whatthisbookcovers
Chapter1,SettingUpYourEnvironment,coversthedifferentsoftwarewe'llbe
installingtogetstartedonthedevelopmentofReactNativeapps.
Chapter2,CreatingaSimpleReactNativeApp,coversthebasicsofbuilding
layoutsandnavigation.Therecipesinthechapterserveasanintroductionto
ReactNativedevelopment,andcoverthebasicfunctionalityfoundinmostany
mobileapp.
Chapter3,ImplementingComplexUserInterfaces–PartI,coversfeatures
includingcustomfontsandcustomreusablethemes.
Chapter4,ImplementingComplexUserInterfaces–PartII,continueswithmore
recipesbasedonUIfeatures.Itcoversfeaturessuchashandlingscreen
orientationchangesandbuildinguserforms.
Chapter5,ImplementingComplexUserInterfaces–PartIII,coversother
commonfeaturesyou'lllikelyneedwhenbuildingcomplexUIs.Thischapter
coversaddingmapsupport,implementingbrowser-basedauthentication,and
creatinganaudioplayer.
Chapter6,AddingBasicAnimationstoYourApp,coversthebasicsofcreating
animations.
Chapter7,AddingAdvancedAnimationstoYourApp,continuesbuildingonthe
previouschapter,withmoreadvancedfeatures.
Chapter8,WorkingwithApplicationLogicandData,introducesustobuilding
appsthathandledata.We'llcovertopicsincludingstoringdatalocallyand
handlingnetworklossgracefully.
Chapter9,ImplementingRedux,coversimplementingtheFluxdatapatterusing
theReduxlibrary.Reduxisabattle-testedwaytohandledataflowinReact
apps,andworksjustaswellinReactNative.
Chapter10,AppWorkflowandThird-PartyPlugins,coversthedifferentmethodsa
developercanusetobuildanapp,alongwithhowtobuildappsusingopen
sourcecode.
Chapter11,AddingNativeFunctionalities–PartI,coversthebasicsofworking
withnativeiOSandAndroidcodeinaReactNativeapp.
Chapter12,AddingNativeFunctionalities–PartII,coversmorecomplex
techniquesforcommunicatingbetweentheReactNativeandnativelayers.
Chapter13,IntegrationwithNativeApplications,coversintegratingReactNative
withanexistingnativeapp.Noteveryappcanbebuiltfromscratch.These
recipesshouldbehelpfulfordeveloperswhoneedtointegratetheirworkwith
anappalreadyintheAppStore.
Chapter14,DeployingYourApp,coversthebasicprocessofdeployingaReact
Nativeapp,aswellasdetailsforusingHockeyApptotrackthemetricsofyour
app.
Chapter15,OptimizingthePerformanceofYourApp,coverssometips,tricks,and
bestpracticesforwritingperformantReactNativecode.
Togetthemostoutofthisbook
Itisassumedthatyouhavethefollowinglevelsofunderstanding:
Youhavesomebasicprogrammingknowledge.
Youarefamiliarwithwebdevelopmentbasics.
Itwillbehelpfulifyoualsohavethefollowing:
React,Vue,orAngularexperience
Downloadtheexamplecodefiles
Youcandownloadtheexamplecodefilesforthisbookfromyouraccountatwww.
packt.com.Ifyoupurchasedthisbookelsewhere,youcanvisitwww.packt.com/support
andregistertohavethefilesemaileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. Loginorregisteratwww.packt.com.
2. SelecttheSUPPORTtab.
3. ClickonCodeDownloads&Errata.
4. EnterthenameofthebookintheSearchboxandfollowtheonscreen
instructions.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthe
folderusingthelatestversionof:
WinRAR/7-ZipforWindows
Zipeg/iZip/UnRarXforMac
7-Zip/PeaZipforLinux
ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/warlywa
re/react-native-cookbook.Incasethere'sanupdatetothecode,itwillbeupdatedon
theexistingGitHubrepository.
Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideos
availableathttps://github.com/PacktPublishing/.Checkthemout!
Downloadthecolorimages
WealsoprovideaPDFfilethathascolorimagesofthescreenshots/diagrams
usedinthisbook.Youcandownloadithere:https://www.packtpub.com/sites/default/f
iles/downloads/9781788991926_ColorImages.pdf.
Conventionsused
Thereareanumberoftextconventionsusedthroughoutthisbook.
CodeInText:Indicatescodewordsintext,databasetablenames,foldernames,
filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitter
handles.Hereisanexample:"We'lluseastateobjectwithalikedBoolean
propertyforthispurpose."
Ablockofcodeissetasfollows:
exportdefaultclassAppextendsReact.Component{
state={
liked:false,
};
handleButtonPress=()=>{
//We'lldefinethecontentonstep6
}
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,the
relevantlinesoritemsaresetinbold:
onststyles=StyleSheet.create({
container:{
flex:1,
},
topSection:{
flexGrow:3,
backgroundColor:'#5BC2C1',
alignItems:'center',
},
Anycommand-lineinputoroutputiswrittenasfollows:
expoinitproject-name
Bold:Indicatesanewterm,animportantword,orwordsthatyouseeonscreen.
Forexample,wordsinmenusordialogboxesappearinthetextlikethis.Hereis
anexample:"ClicktheComponentstab,andinstallasimulatorfromthelistof
providedsimulators."
Warningsorimportantnotesappearlikethis.
Tipsandtricksappearlikethis.
Sections
Inthisbook,youwillfindseveralheadingsthatappearfrequently(Getting
ready,Howtodoit...,Howitworks...,There'smore...,andSeealso).
Togiveclearinstructionsonhowtocompletearecipe,usethesesectionsas
follows:
Gettingready
Thissectiontellsyouwhattoexpectintherecipeanddescribeshowtosetup
anysoftwareoranypreliminarysettingsrequiredfortherecipe.
Howtodoit…
Thissectioncontainsthestepsrequiredtofollowtherecipe.
Howitworks…
Thissectionusuallyconsistsofadetailedexplanationofwhathappenedinthe
previoussection.
There'smore…
Thissectionconsistsofadditionalinformationabouttherecipeinordertomake
youmoreknowledgeableabouttherecipe.
Seealso
Thissectionprovideshelpfullinkstootherusefulinformationfortherecipe.
Getintouch
Feedbackfromourreadersisalwayswelcome.
Generalfeedback:Ifyouhavequestionsaboutanyaspectofthisbook,mention
thebooktitleinthesubjectofyourmessageandemailusat
customercare@packtpub.com.
Errata:Althoughwehavetakeneverycaretoensuretheaccuracyofour
content,mistakesdohappen.Ifyouhavefoundamistakeinthisbook,wewould
begratefulifyouwouldreportthistous.Pleasevisitwww.packt.com/submit-errata,
selectingyourbook,clickingontheErrataSubmissionFormlink,andentering
thedetails.
Piracy:Ifyoucomeacrossanyillegalcopiesofourworksinanyformonthe
Internet,wewouldbegratefulifyouwouldprovideuswiththelocationaddress
orwebsitename.Pleasecontactusatcopyright@packt.comwithalinktothe
material.
Ifyouareinterestedinbecominganauthor:Ifthereisatopicthatyouhave
expertiseinandyouareinterestedineitherwritingorcontributingtoabook,
pleasevisitauthors.packtpub.com.
Reviews
Pleaseleaveareview.Onceyouhavereadandusedthisbook,whynotleavea
reviewonthesitethatyoupurchaseditfrom?Potentialreaderscanthenseeand
useyourunbiasedopiniontomakepurchasedecisions,weatPacktcan
understandwhatyouthinkaboutourproducts,andourauthorscanseeyour
feedbackontheirbook.Thankyou!
FormoreinformationaboutPackt,pleasevisitpackt.com.
SettingUpYourEnvironment
TheReactNativeecosystemhasevolvedquiteabitsincethefirstedition.The
opensourcetoolExpo.io,inparticular,hasstreamlinedboththeproject
initializationanddevelopmentphases,makingworkinginReactNativeeven
moreofapleasurethanitalreadywasinversion0.36.
WiththeExpoworkflow,you'llbeabletobuildnativeiOSandAndroid
applicationsusingonlyJavaScript,workintheiOSsimulatorandAndroid
emulatorwithlivereload,andeffortlesslytestyourapponanyreal-worlddevice
viaExpo'sapp.Untilyouneedaccesstonativecode(say,tointegratewith
legacynativecodefromaseparatecodebase),youcandevelopyourapplication
entirelyinJavaScriptwithouteverneedingtouseXcodeorAndroidStudio.If
yourprojecteverneedstoevolveintonativecodesupport,Expoprovidesthe
abilitytodetachyourproject,whichchangesyourappintonativecodeforusein
XcodeandAndroidStudio.FormoreinformationondetachingyourExpo
project,pleaseseeChapter11,AddingNativeFunctionality–PartI.
ExpoisanawesomewaytobuildfullyfeaturedappsforAndroidandiOS
devices,withouteverhavingtodealwithnativecode.Let'sgetstarted!
Wewillcoverthefollowingtopicsinthischapter:
Installingdependencies
Initializingyourfirstapplication
Runningyourapplicationinasimulator/emulator
Runningyourapplicationonarealdevice
Technicalrequirements
Thischapterwillcoverinstallingthetoolsyou'llbeusingthroughoutthisbook.
Theyinclude:
Expo
Xcode(foriOSsimulator,macOSonly)
Genymotion(forAndroidemulator)
Node.js
Watchman
Installingdependencies
ThefirststeptowardbuildingourfirstReactNativeapplicationisinstallingthe
dependenciesinordertogetstarted.
InstallingXcode
Asmentionedintheintroductionofthischapter,Expoprovidesuswitha
workflowinwhichwecanavoidworkinginXcodeandAndroidStudio
altogether,sowecandevelopsolelyinJavaScript.However,inordertorunyour
appintheiOSsimulator,youwillneedtohaveXcodeinstalled.
XcoderequiresmacOS,andthereforerunningyourReactNativeapplicationinaniOS
simulatorisonlypossibleonmacOS.
XcodeshouldbedownloadedfromtheAppStore.YoucansearchtheAppStore
forXcode,orusethefollowinglink:
https://itunes.apple.com/app/xcode/id497799835.
Xcodeisasizabledownload,soexpectthisparttotakealittlewhile.Onceyou
haveinstalledXcodeviatheAppStore,youcanrunitviatheApplicationsfolder
intheFinder:
1. ThisisthefirstscreenyouwillseewhenlaunchingXcode.Note,ifthisis
thefirsttimeyou'veinstalledXcode,youwillnotseerecentprojectslisted
downtheright-handside:
2. Fromthemenubar,chooseXcode|Preferences...asfollows:
3. ClicktheComponentstab,andinstallasimulatorfromthelistofprovided
simulators:
4. Onceinstalled,youcanopenthesimulatorfromthemenubar:Xcode|
OpenDeveloperTool|Simulator:
InstallingGenymotion
GenymotionPersonalEditionisafree,feature-richAndroidemulator
recommendedbytheExpoteam.GenymotionalsorequirestheVirtualBox
virtualizer,sowewillinstallthatfirst:
1. DownloadtheVirtualBoxapplicationfromhttps://www.virtualbox.org/wiki/Down
loadsandrunit.Followtheinstallationinstructions:
2. Uponinstallation,youwilllikelybepromptedbyyourOStoallow
VirtualBoxtorun:
3. YoumustfollowtheseinstructionsforVirtualBoxtoworkproperly.From
themenubar,choosetheSystemPreferences...optionfromtheApple
menu:
4. ChooseSecurity&PrivacyfromtheSystemPreferenceswindow:
5. OntheGeneraltab,youwillseeamessagestatingthatsoftwarefrom
"OracleAmerica,Inc."wasblocked.ClicktheAllowbuttonnexttothis
message:
6. OnceVirtualBoxisinstalled,youmustrestartyourmachine!After
restarting,downloadGenymotionPersonalEditionfromhttps://www.genymotio
n.com/fun-zone/.Youwillneedtomakeanaccounttodownloadthesoftware.
Onceloggedin,thesitewillallowyoutodownloadGenymotionfromthe
linkprovidedpreviously.Intheinstaller,dragGenymotion.appandGenymotion
Shell.apptotheApplicationsfolder:
7. Oncetheinstallationiscomplete,runGenymotionfrom
theApplicationsfolder.ClickthePersonalUselinkatthebottomof
theUsagenoticedialogbox:
8. Onceyouagreetothetermsofuse,youshouldbepromptedtoinstalla
virtualdevice.SelectYes:
9. TheExpodocumentationrecommendsusingaGoogleNexus5device,
alongwithanyversionofAndroid.IntheDevicemodelmenu
chooseGoogleNexus5,thenselectoneoftheAndroidversionstoinstall.
I'vechosenthenewestversionavailable:
10. Oncethevirtualdeviceisdownloadedandinstalled,youwillbereturnedto
themainview.Fromhere,choosethevirtualdeviceyou'veinstalled,and
clickStarttoruntheemulator:
InstallingNode.js
Node.jsisaJavaScriptruntimebuiltonChrome'sV8JavaScriptengine,andis
designedtobuildscalablenetworkapplications.NodeallowsJavaScripttobe
executedinaTerminal,andisanindispensabletoolforanywebdeveloper.For
moreinformationonwhatNode.jsis,youcanreadtheproject'sAboutNode.js
pageathttps://nodejs.org/en/about/.
AccordingtotheExpoinstallationdocumentation,Node.jsisnottechnically
required,butassoonasyoustartactuallybuildingsomething,you'llwantto
haveit.Node.jsitselfisoutsidethescopeofthisbook,butyoucancheckout
theFurtherreadingsectionattheendofthischapterformoreresourceson
workingwithNode.js.
TherearenumerousmethodstoinstallNode.js,anditisthereforedifficultto
recommendaparticularinstallationmethod.OnmacOS,youcaninstallNode.js
inoneofthefollowingways:
DownloadingandinstallingNode.jsfromtheproject'ssiteathttps://nodejs.o
rg/en/download/.
InstallingviaHomebrew.IfyouarefamiliarwithHomebrew,thisprocessis
explainedsuccinctlyathttps://medium.com/@katopz/how-to-install-specific-nodejs-
version-c6e1cec8aa11.
InstallingviaNodeVersionManager
(NVM;https://github.com/creationix/nvm).NVMallowsyoutoinstallmultiple
versionsofNode.jsandeasilyswitchbetweenthem.Usetheinstructions
providedintherepository'sREADMEtoinstallNVM.Thisisthe
recommendedmethod,duetoitsflexibility,aslongasyou'recomfortable
workingintheTerminal.
InstallingExpo
TheExpoprojectusedtohaveaGUI-baseddevelopmentenvironmentcalledthe
ExpoXDE,whichhasbeenreplacedwithabrowser-basedGUIcalledtheExpo
DeveloperTools.SincetheExpoXDEhasbeendeprecated,creatingnewExpo
appsisnowalwaysdoneusingtheExpoCLI.Thiscanbeinstalledusingnpm
(NodePackageManager,whichcomesaspartofNode.js)viatheTerminalwith
thefollowingcommand:
npminstallexpo-cli-g
We'llbeusingExpoquiteabitthroughoutthisbooktocreateandbuildout
ReactNativeapplications,particularlythoseappsthatdonotneedaccessto
nativeiOSorAndroidcode.ApplicationsbuiltwithExpohavesomeverynice
advantagesfordevelopment,helpingobfuscatenativecode,streamliningapp
publishingandpushnotifications,andprovidingalotofusefulfunctionality
builtintotheExpoSDK.FormoreinformationonhowExpoworks,andhowit
fitsintothebiggerpictureofReactNativedevelopment,seeChapter10,App
WorkflowandThird-PartyPlugins.
InstallingWatchman
WatchmanisatoolusedinternallybyReactNative.Itspurposeistowatchfiles
forupdates,andtriggerresponses(suchaslivereloading)whenchangesoccur.
TheExpodocumentationrecommendsinstallingWatchman,sinceithasbeen
reportedthatsomemacOSusershaverunintoissueswithoutit.The
recommendedmethodforinstallingWatchmanisviaHomebrew.Themissing
packagemanagerformacOS,Homebrewallowsyoutoinstallawidearrayof
usefulprogramsstraightfromyourTerminal.It'sanindispensabletoolthat
shouldbeineverydeveloper'stoolbag:
1. Ifyoudon'thaveHomebrewinstalledalready,runthefollowingcommand
intheTerminaltoinstallit(youcanreadmoreaboutitandviewtheofficial
documentationathttps://brew.sh/):
/usr/bin/ruby-e"$(curl-fsSLhttps://raw.githubusercontent.com/Homebrew/install/master/install)"
2. OnceHomebrewhasbeeninstalled,runthefollowingtwocommandsin
Terminaltoinstallwatchman:
brewupdate
brewinstallwatchman
Initializingyourfirstapp
Thehardpartofthesetupisdone!Fromhereonout,wecanusethemagic
providedbyExpotoeasilycreatenewappsfordevelopment.
We'llcreateourfirstappusingExpoviatheExpoCLI.Makinganew
applicationisassimpleasrunningthefollowing:
expoinitproject-name
Runningthiscommandwillfirstpromptyouwhichtypeofappyou'dliketo
create:eitherablankapp,whichhasnofunctionalityadded,oratabsapp,which
willcreateanewappwithminimaltabnavigation.Fortherecipesinthisbook,
we'llbeusingtheblankappoption.
Onceyou'veselectedyourpreferredapplicationtype,anew,emptyExpo-
poweredReactNativeappinanewproject-namedirectoryiscreated,alongwith
allofthedependenciesneededtostartdevelopingrightaway.Allyouneedtodo
isbegineditingtheApp.jsfileinthenewprojectdirectorytogettowork.
Torunournewapp,wecancdintothedirectory,thenusetheexpostart
command.Thiswillautomaticallybuildandservetheapp,andopenanew
browserwindowwiththeExpoDeveloperToolsforyourin-developmentReact
Nativeapp.
ForalistofalloftheavailablecommandsfortheExpoCLI,checkoutthe
documentationathttps://docs.expo.io/versions/latest/guides/expo-cli.html.
Withourfirstapplicationcreated,let'smoveontorunningtheapplicationinan
iOSsimulatorand/orAndroidemulator.
Runningyourappina
simulator/emulator
Youhavecreatedanewproject,andstartedrunningthatprojectwithExpointhe
laststep.OncewestartmakingchangestoourReactNativecode,wouldn'titbe
nicetoseetheresultsofthosechanges?ThankstoExpo,runningyourprojectin
theinstallediOSsimulatororAndroidemulatorhasalsobeenstreamlined.
RunningyourapponaniOS
simulator
ThankstoExpo,runningyourappintheXcodesimulatoronlytakesafew
clicks.Toavoidpotentialissues,it'seasiesttomakesureyoursimulatoris
alreadyrunningbeforeyouattempttoopenanExpo-basedReactNativeappina
simulator:
1. OpenXcode.
2. OpentheSimulatorfromthemenubar:Xcode|OpenDeveloper
Tool|Simulator:
3. Waituntilthesimulatorhasloadedtheoperatingsystem:
4. IntheExpoDeveloperToolsbrowserwindow,selectRunoniOS
Simulator.
5. ThefirsttimeyourunaReactNativeappontheiOSsimulatorviaRunon
iOSSimulator,theExpoappwillbeinstalledonthesimulator,andyour
appwillautomaticallybeopenedwithintheExpoapp.ThesimulatediOS
willaskifyouwanttoOpenin"Expo"?.ChooseOpen:
6. Uponloading,youwillseetheExpoDevelopermenu.Youcantoggle
betweenthismenuandyourReactNativeappbypressingcommandkey+
Donyourkeyboard:
RunningyourapponanAndroid
emulator
We'llusetheAndroidemulatorrecommendedbytheExpoteam,Genymotion,
whichweinstalledinarecipeearlierinthischapter.AswiththeiOSsimulator,
ensurethatyouremulatorisrunningbeforetryingtolaunchtheExpo-basedapp
intheemulator:
1. OpenGenymotion.
2. ClicktheStartbutton:
3. TheemulatorwillopenandloadtheAndroidhomescreen.Notethat
loadingtheemulatorcantakesometime,sodon'tworryifit'stakingalittle
longerthanyou'dexpect:
4. IntheExpoDeveloperToolsbrowserwindow,selectRunonAndroid
device/emulator.
5. IntheAndroidemulator,youwillbepromptedtoenable"Permitdrawing
overotherapps".ClickOK:
6. ToggleonthePermitdrawingoverotherappsoptiononthenextscreen:
7. Thenhitthebackbuttonatthebottomoftheemulator:
8. OncetheJavaScriptbundleisloaded,youwillseeyourblankReactNative
appintheemulator:
9. YoucantogglebetweenyourReactNativeappandtheExpoDeveloper
menu,alistofhelpfulfeaturesfordevelopment,bypressingcommand
key+Monyourkeyboard.TheExpoDevelopermenushouldlook
somethinglikethis:
Asyoumayhavenoticed,youhavetheoptiontoEnableHotReloading.With
hotreloadingenabled,thechangeswillupdateontheemulator/simulatorright
away,withoutanyneedtoreloadtheJavaScriptbundlefirst.
Runningyourapponarealdevice
Expoalsomakesrunningyourdevelopmentapponarealdeviceaseasyas
runningyourapponasimulator.WiththeclevercombinationofthenativeExpo
appandaQRcode,runningonarealdeviceisonlyafewclicksandtapsaway!
SincetheprocessisthesameforbothiOSandAndroid,onlytheiOSprocess
willbecoveredindepth.
RunningyourapponaniPhoneor
Android
Youcangetthein-developmentapprunningonyourphoneinthreesimple
steps:
1. OpentheAppStoreonyouriPhone,ortheGooglePlayStoreonyour
Androiddevice.
2. SearchforanddownloadtheExpoClientapp.
3. Whileyourappisrunningonyourdevelopmentmachine,youshouldalso
havetheExpoDeveloperToolsopeninabrowser.YoushouldseeaQR
codeatthebottomoftheleft-handsidemenuoftheExpoDeveloperTools.
UsetheiPhone'snativeCameraapp,ortheScanQRCodebuttoninthe
ExpoClientapponAndroid,toscantheQRcode.Thiswillopenyourin-
developmentapponthedevicewithintheExpoClientapp.
YourReactNativeappshouldnowberunningonyourrealdevice,fully
equippedwithlivereload!Youcanalsoshakethedevicetotogglebetweenyour
ReactNativeappandtheExpoDevelopermenu.
Summary
Inthischapter,we'vegonethroughallthestepsrequiredforgettingstartedwith
developingReactNativeapps,includinginitializinganewproject,emulating
runningyournewprojectonyourcomputer,andrunningyourdevelopmentapp
onreal-worlddevices.ThankstothepowerofExpo,it'seasiertojumpinand
startworkingthaneverbefore.
Nowthatyou'vegoteverythingsetup,it'stimetostartbuilding!
Furtherreading
Here'salistofotherresourcescoveringsimilartopics:
TheExpoinstallationdocumentationathttps://docs.expo.io/versions/latest/int
roduction/installation.html.
Node.jsWebDevelopmentathttps://www.packtpub.com/mapt/book/web_development/9
781785881503.
IntroducingHotReloading-ReactNativeathttps://facebook.github.io/react-n
ative/blog/2016/03/24/introducing-hot-reloading.html.Thisblogpostfromthe
ReactNativeteamdescribeshowHotReloadingworksindepth.
PublishingwithExpoathttps://docs.expo.io/versions/latest/guides/publishing.ht
ml.Expohasapublishfeaturethatallowsyoutoshareyourin-development
ReactNativeapplicationwithfellowdevelopersbycreatingapersistent
URL.
ExpoSnackathttps://snack.expo.io.Similartocodepen.ioorjsfiddle.net,Snack
letsyouliveeditaReactNativeappinthebrowser!
CreatingaSimpleReactNativeApp
Inthischapter,we'llcoverthefollowingrecipes:
Addingstylestoelements
Usingimagestomimicavideoplayer
Creatingatogglebutton
Displayingalistofitems
Usingflexboxtocreatealayout
Settingupandusingnavigation
ReactNativeisafast-growinglibrary.Overthelastyearandahalfbefore
writingthis,ithasbecomeverypopularamongtheopensourcecommunity.
There'sanewreleaseeveryotherweekthatimprovesperformance,addsnew
components,orprovidesaccesstonewAPIsonthedevice.
Inthischapter,we'lllearnaboutthemostcommoncomponentsinthelibrary.To
stepthroughalloftherecipesinthischapter,we'llhavetocreateanew
application,somakesureyouhaveyourenvironmentupandrunning.
Addingstylestoelements
Wehaveseveralcomponentsatourdisposal,butcontainersandtextarethemost
commonandusefulcomponentstocreatelayoutsorothercomponents.Inthis
recipe,we'llseehowtousecontainersandtext,butmostimportantlywe'llsee
howstylesworkinReactNative.
We'llcreateaUIforasimplemusicplayer;wewon'tbeusingiconsfornow,but
we'lladdthemlater.
Gettingready
Followtheinstructionsinthepreviouschapterinordertocreateanew
application.We'llnamethisapplicationfake-music-player.
WhencreatinganewapplicationwithExpo,asmallamountofboilerplatecode
willbeaddedtotheApp.jsfileintherootfolder.Thiswillbethestartingpointof
anyReactNativeapplicationyoubuild.Feelfreetoremoveallboilerplateatthe
beginningofeachrecipe,asallcode(includingwhat'susedintheApp.js
boilerplate)willbediscussed.
Howtodoit...
1. IntheApp.jsfile,we'regoingtocreateastatelesscomponent.This
componentwillmimicasmallmusicplayer.Fornow,it'llonlydisplaythe
nameofthesongandabartoshowtheprogress.Thefirststepisimporting
ourdependencies:
importReactfrom'react';
import{StyleSheet,Text,View}from'react-native';
2. Oncewe'veimportedthedependencies,wecanbuildoutthecomponent:
exportdefaultclassAppextendsReact.Component{
render(){
constname='01-BlueBehindGreenBloches';
return(
<Viewstyle={styles.container}>
<Viewstyle={styles.innerContainer}/>
<Textstyle={styles.title}>
<Textstyle={styles.subtitle}>Playing:</Text>{name}
</Text>
</View>
);
}
}
3. Wehaveourcomponentready,sonowweneedtoaddsomestyles,toadd
colorsandfonts:
conststyles=StyleSheet.create({
container:{
margin:10,
marginTop:100,
backgroundColor:'#e67e22',
borderRadius:5,
},
innerContainer:{
backgroundColor:'#d35400',
height:50,
width:150,
borderTopLeftRadius:5,
borderBottomLeftRadius:5,
},
title:{
fontSize:18,
fontWeight:'200',
color:'#fff',
position:'absolute',
backgroundColor:'transparent',
top:12,
left:10,
},
subtitle:{
fontWeight:'bold',
},
});
4. Aslongasoursimulatorandemulatorarerunningourapplication,we
shouldseethechanges:
Howitworks...
Instep1,weincludedthedependenciesofourcomponent.Inthiscase,weused
View,whichisacontainer.Ifyou'refamiliarwithwebdevelopment,Viewissimilar
todiv.WecouldaddmoreViewsinsideotherViews,Texts,Lists,andanyother
customcomponentthatwecreateorimportfromathird-partylibrary.
Asyoucansee,thisisastatelesscomponent,whichmeansitdoesn'thaveany
state;it'sapurefunctionanddoesn'tsupportanyofthelifecyclemethods.
We'redefininganameconstantinthecomponent,butinreal-worldapplications
thisdatashouldcomefromtheprops.Inthereturn,we'redefiningthe
JavaScriptXML(JSX)thatwe'regoingtoneedtorenderourcomponent,
alongwithareferencetothestyles.
Eachcomponenthasapropertycalledstyle.Thispropertyreceivesanobject
withallofthestylesthatwewanttoapplytothegivencomponent.Stylesare
notinherited(exceptfortheTextcomponent)bythechildcomponents,which
meansweneedtosetindividualstylesforeachcomponent.
Instep3,wedefinedthestylesforourcomponent.We'reusingtheStyleSheetAPI
tocreateallofourstyles.Wecouldhaveusedaplainobjectcontainingthe
styles,butbyusingtheStyleSheetAPIinsteadofanobject,wegainsome
performanceoptimizations,asthestyleswillbereusedforeveryrenderer,as
opposedtocreatinganobjecteverytimetherendermethodgetsexecuted.
There'smore...
I'dliketocallyourattentiontothedefinitionofthetitlestyleinstep3.Here,
we'vedefinedapropertycalledbackgroundColorandsettransparentasitsvalue.Asa
goodexercise,let'scommentthislineofcodeandseetheresult:
OniOS,thetextwillhaveanorangebackgroundcoloranditmightnotbewhat
wereallywanttohappeninourUI.Inordertofixthis,weneedtosetthe
backgroundcolorofthetextastransparent.Butthequestionis,whyisthis
happening?ThereasonisthatReactNativeaddssomeoptimizationstothetext
bysettingthecolorfromtheparent'sbackgroundcolor.Thiswillimprovethe
renderingperformancebecausetherenderingenginewon'thavetocalculatethe
pixelsaroundeachletterofthetextandtherenderingwillbeexecutedfaster.
Thinkcarefullywhensettingthebackgroundcolortotransparent.Ifthecomponentisgoingto
beupdatingthecontentveryfrequently,theremightbesomeperformanceissueswithtext,
especiallyifthetextistoolong.
Usingimagestomimicavideoplayer
ImagesareanimportantpartofanyUI,whetherweusethemtodisplayicons,
avatars,orpictures.Inthisrecipe,we'lluseimagestocreateamockvideo
player.We'llalsodisplaytheiconsfromthelocaldeviceandalargeimagefrom
aremoteserver(hostedbyFlickr).
Gettingready
Inordertofollowthestepsinthisrecipe,let'screateanewapplication.We're
goingtonameitfake-video-player.
We'regoingtodisplayafewimagesinourapplicationtomimicavideoplayer,
soyou'llneedcorrespondingimagesforyourapplication.Irecommendusingthe
iconsIusedbydownloadingthemfromtherepositoryforthisrecipeonGitHub
athttps://github.com/warlyware/react-native-cookbook/tree/master/chapter-2/fake-video-pla
yer/images.
Howtodoit...
1. Thefirstthingwe'regoingtodoiscreateanewfoldercalledImagesinthe
rootoftheproject.Addtheimagesyou'vedownloadedtothenewfolder.
2. IntheApp.jsfile,weincludeallofthedependencieswe'llneedforthis
component:
importReactfrom'react';
import{StyleSheet,View,Image}from'react-native';
3. Weneedtorequiretheimagesthat'llbedisplayedinourcomponent.By
definingtheminconstants,wecanusethesameimageindifferentplaces:
constplayIcon=require('./images/play.png');
constvolumeIcon=require('./images/sound.png');
consthdIcon=require('./images/hd-sign.png');
constfullScreenIcon=require('./images/full-screen.png');
constflower=require('./images/flower.jpg');
constremoteImage={uri:`https://farm5.staticflickr.com/4702/24825836327_bb2e0fc39b_b.jpg`};
4. We'regoingtouseastatelesscomponenttorendertheJSX.We'lluseallof
theimageswe'vedeclaredinthepreviousstep:
exportdefaultclassAppextendsReact.Component{
render(){
return(
<Viewstyle={styles.appContainer}>
<ImageBackgroundsource={remoteImage}style=
{styles.videoContainer}resizeMode="contain">
<Viewstyle={styles.controlsContainer}>
<Imagesource={volumeIcon}style={styles.icon}/>
<Viewstyle={styles.progress}>
<Viewstyle={styles.progressBar}/>
</View>
<Imagesource={hdIcon}style={styles.icon}/>
<Imagesource={fullScreenIcon}style={styles.icon}/>
</View>
</ImageBackground>
</View>
);
}
};
5. Oncewehavetheelementsthatwe'regoingtorender,weneedtodefinethe
stylesforeachelement:
conststyles=StyleSheet.create({
flower:{
flex:1,
},
appContainer:{
flex:1,
justifyContent:'center',
alignItems:'center',
},
videoContainer:{
backgroundColor:'#000',
flexDirection:'row',
flex:1,
justifyContent:'center',
alignItems:'center',
},
controlsContainer:{
padding:10,
backgroundColor:'#202020',
flexDirection:'row',
alignItems:'center',
marginTop:175,
},
icon:{
tintColor:'#fff',
height:16,
width:16,
marginLeft:5,
marginRight:5,
},
progress:{
backgroundColor:'#000',
borderRadius:7,
flex:1,
height:14,
margin:4,
},
progressBar:{
backgroundColor:'#bf161c',
borderRadius:5,
height:10,
margin:2,
paddingTop:3,
width:80,
alignItems:'center',
flexDirection:'row',
},
});
6. We'redone!Now,whenyouviewtheapplication,youshouldsee
somethinglikethefollowing:
Howitworks...
Instep2,werequiredtheImagecomponent.Thisisthecomponentresponsiblefor
renderingimagesfromthelocalfilesystemonthedeviceorfromaremote
server.
Instep3,werequiredalloftheimages.It'sgoodpracticetorequiretheimages
outsideofthecomponentinordertoonlyrequirethemonce.Oneveryrenderer,
ReactNativewillusethesameimage.Ifweweredealingwithdynamicimages
fromaremoteserver,thenwe'dneedtorequirethemoneveryrenderer.
Therequirefunctionacceptsthepathoftheimageasaparameter.Thepathis
relativetothefolderthatourclassisin.Forremoteimages,weneedtousean
objectdefininguriforwhereourfileis.
Instep4,astatelesscomponentwasdeclared.WeusedremoteImageasthe
backgroundofourapplicationviaanImageBackgroundelement,sinceImageelements
cannothavechildelements.Thiselementactssimilarlytothebackground-url
propertyinCSS.
ThesourcepropertyofImageacceptsanobjecttoloadremoteimagesora
referencetotherequiredfile.It'sveryimportanttoexplicitlyrequireevery
imagethatwewanttousebecausewhenweprepareourapplicationfor
distribution,imageswillbeaddedtothebundleautomatically.Thisisthereason
weshouldavoiddoinganythingdynamic,suchasthefollowing:
consticonName=playing?'pause':'play';
consticon=require(iconName);
Theprecedingcodewon'tincludetheimagesinthefinalbundle.Asaresult,
we'llhaveerrorswhentryingtoaccesstheseimages.Instead,weshouldrefactor
ourcodetosomethinglikethis:
constpause=require('pause');
constplay=require('playing');
consticon=playing?pause:play;
Thisway,thebundlewillincludebothimageswhenpreparingourapplication
fordistribution,andwecandecidewhichimagetodisplaydynamicallyat
runtime.
Instep5,wedefinedthestyles.Mostofthepropertiesareself-explanatory.Even
thoughtheimageswe'reusingforiconsarewhite,I'veaddedthetintColor
propertytoshowhowitcanbeusedtocolorimages.Giveitatry!Change
tintColorto#f00andwatchtheiconsturnred.
Flexboxisbeingusedtoaligndifferentportionsofthelayout.FlexboxinReact
Nativebehavesessentiallythesameasitdoesinwebdevelopment.We'lldiscuss
flexboxmoreintheUsingflexboxtocreatealayoutrecipelaterinthischapter,
butthecomplexitiesofflexboxitselfareoutsidethescopeofthisbook.
Creatingatogglebutton
ButtonsareanessentialUIcomponentineveryapplication.Inthisrecipe,we'll
createatogglebutton,whichwillbeunselectedbydefault.Whentheusertaps
onit,we'llchangethestylesappliedtothebuttontomakeitappearselected.
We'lllearnhowtodetectthetapevent,useanimageastheUI,keepthestateof
thebutton,andaddstylesbasedonthecomponentstate.
Gettingready
Let'screateanewapp.We'regoingtonameittoggle-button.We'regoingtouse
oneimageinthisrecipe.Youcandownloadtheassetsforthisrecipefromthe
correspondingrepositoryhostedonGitHubathttps://github.com/warlyware/react-nat
ive-cookbook/tree/master/chapter-2/toggle-button/images.
Howtodoit...
1. We'regoingtocreateanewfoldercalledimagesintherootoftheprojectand
addtheheartimagetothenewfolder.
2. Let'simportthedependenciesforthisclassnext:
importReact,{Component}from'react';
import{
StyleSheet,
View,
Image,
Text,
TouchableHighlight,
}from'react-native';
constheartIcon=require('./images/heart.png');
3. Forthisrecipe,weneedtokeeptrackofwhetherthebuttonhasbeen
pressed.We'lluseastateobjectwithalikedBooleanpropertyforthis
purpose.Theinitialclassshouldlooklikethis:
exportdefaultclassAppextendsReact.Component{
state={
liked:false,
};
handleButtonPress=()=>{
//Definedinalaterstep
}
render(){
//Definedinalaterstep
}
}
4. Weneedtodefinethecontentofournewcomponentinsidetherender
method.Here,we'regoingtodefinetheImagebuttonandaTextelement
underneathit:
exportdefaultclassAppextendsReact.Component{
state={
liked:false,
};
handleButtonPress=()=>{
//Definedinalaterstep
}
render(){
return(
<Viewstyle={styles.container}>
<TouchableHighlight
style={styles.button}
underlayColor="#fefefe"
>
<Image
source={heartIcon}
style={styles.icon}
/>
</TouchableHighlight>
<Textstyle={styles.text}>Doyoulikethisapp?</Text>
</View>
);
}
}
5. Let'sdefinesomestylestosetdimensions,position,margins,colors,andso
on:
conststyles=StyleSheet.create({
container:{
marginTop:50,
alignItems:'center',
},
button:{
borderRadius:5,
padding:10,
},
icon:{
width:180,
height:180,
tintColor:'#f1f1f1',
},
liked:{
tintColor:'#e74c3c',
},
text:{
marginTop:20,
},
});
6. Whenweruntheprojectonthesimulators,weshouldhavesomething
similartothefollowingscreenshot:
7. Inordertorespondtothetapevent,weneedtodefinethecontentofthe
handleButtonPressfunctionandassignitasacallbacktotheonPressproperty:
handleButtonPress=()=>{
this.setState({
liked:!this.state.liked,
});
}
render(){
return(
<Viewstyle={styles.container}>
<TouchableHighlight
onPress={this.handleButtonPress}
style={styles.button}
underlayColor="#fefefe"
>
<Image
source={heartIcon}
style={styles.icon}
/>
</TouchableHighlight>
<Textstyle={styles.text}>Doyoulikethisapp?</Text>
</View>
);
}
8. Ifwetestourcode,wewon'tseeanythingchangingontheUI,eventhough
thestateonthecomponentchangeswhenwepressthebutton.Let'sadda
differentcolortotheimagewhenthestatechanges.Thatway,we'llbeable
toseearesponsefromtheUI:
render(){
constlikedStyles=this.state.liked?styles.liked:undefined;
return(
<Viewstyle={styles.container}>
<TouchableHighlight
onPress={this.handleButtonPress}
style={styles.button}
underlayColor="#fefefe"
>
<Image
source={heartIcon}
style={[styles.icon,likedStyles]}
/>
</TouchableHighlight>
<Textstyle={styles.text}>Doyoulikethisapp?</Text>
</View>
);
}
Howitworks...
Instep2,weimportedtheTouchableHighlightcomponent.Thisisthecomponent
responsibleforhandlingthetouchevent.Whentheusertouchestheactivearea,
thecontentwillbehighlightedbasedontheunderlayColorvaluewehaveset.
Instep3,wedefinedthestateofComponent.Inthiscase,there'sonlyoneproperty
onthestate,butwecanaddasmanyasneeded.InChapter3,Implementing
ComplexUserInterfaces–PartI,we'llseemorerecipesabouthandlingthestate
inmorecomplexscenarios.
Instep6,weusedthesetStatemethodtochangethevalueofthelikedproperty.
ThismethodisinheritedfromtheComponentclassthatwe'reextending.
Instep7,basedonthecurrentstateofthelikedproperty,weusedthestylestoset
thecoloroftheimagetoredorwereturnedundefinedtoavoidapplyingany
styles.WhenassigningthestylestotheImagecomponent,weusedanarrayto
assignmanyobjects.Thisisveryhandybecausethecomponentwillmergeallof
thestylesintoonesingleobjectinternally.Theobjectswiththehighestindex
willoverwritethepropertieswiththelowestobjectindexinthearray:
There'smore...
Inarealapplication,we'regoingtouseseveralbuttons,sometimeswithanicon
alignedtotheleft,alabel,differentsizes,colors,andsoon.It'shighly
recommendedtocreateareusablecomponenttoavoidduplicatingcodeallover
ourapp.InChapter3,ImplementingComplexUserInterfaces–PartI,we'llcreate
abuttoncomponenttohandlesomeofthesescenarios.
Displayingalistofitems
Listsareeverywhere:alistofordersintheuser'shistory,alistofavailableitems
inastore,alistofsongstoplay.Nearlyanyapplicationwillneedtodisplay
somekindofinformationinalist.
Forthisrecipe,we'regoingtodisplayseveralitemsinalistcomponent.We're
goingtodefineaJSONfilewithsomedata,thenwe'regoingtoloadthisfile
usingasimplerequiretofinallyrendereachitemwithanicebutsimplelayout.
Gettingready
Let'sstartbycreatinganemptyapp.We'llnamethisapplicationlist-items.We're
goingtoneedanicontodisplayoneachitem.Theeasiestwaytogetimagesis
todownloadthemfromthisrecipe'srepositoryhostedonGitHubathttps://github
.com/warlyware/react-native-cookbook/tree/master/chapter-2/toggle-button/images.
Howtodoit...
1. We'llstartbycreatinganimagesfolderandaddingbasket.pngtoit.Also,
createanemptyfileintherootoftheprojectcalledsales.json.
2. Insidethesales.jsonfile,we'lldefinethedatathatwe'regoingtodisplayin
thelist.Here'ssomesampledata:
[
{
"items":5,
"address":"140Broadway,NewYork,NY11101",
"total":38,
"date":"May15,2016"
}
]
3. Toavoidclutteringthepagesofthisbook,I'veonlydefinedonerecord,but
goaheadandaddmorecontenttothearray.Copyingandpastingthesame
objectmultipletimeswilldothetrick.Inaddition,youcouldchangesome
valuesonthedatasothateachitemdisplaysuniquedataintheUI.
4. InourApp.jsfile,let'simportthedependencieswe'llneed:
importReact,{Component}from'react';import{StyleSheet,View,ListView,Image,Text,}from'react-native';importdatafrom'./sales.json';constbasketIcon=require('./images/basket.png');
5. Now,weneedtocreatetheclasstorenderthelistofitems.We'regoingto
keepthesalesdataonthestate;thatway,wecouldinsertorremove
elementseasily:
exportdefaultclassAppextendsReact.Component{
constructor(props){
super(props);
constdataSource=newListView.DataSource({
rowHasChanged:(r1,r2)=>r1!==r2
});
this.state={
dataSource:dataSource.cloneWithRows(data),
};
}
renderRow(record){
//Definedinalaterstep
}
render(){
//Definedinalaterstep
}
}
6. Intherendermethod,weneedtodefinetheListViewcomponentandwe'lluse
therenderRowmethodtorendereachitem.ThedataSourcepropertydefinesthe
arrayofelementsthatwe'regoingtorenderonthelist:
render(){
return(
<Viewstyle={styles.mainContainer}>
<Textstyle={styles.title}>Sales</Text>
<ListViewdataSource={this.state.dataSource}renderRow={this.renderRow}/>
</View>
);
}
7. Now,wecandefinethecontentsofrenderRow.Thismethodreceiveseach
objectcontainingalloftheinformationweneed.We'regoingtodisplaythe
datainthreecolumns.Inthefirstcolumn,we'llshowanicon;inthesecond
column,we'llshowthenumberofitemsforeachsaleandtheaddresswhere
thisorderwillship;andthethirdcolumnwilldisplaythedateandthetotal:
return(
<Viewstyle={styles.row}>
<Viewstyle={styles.iconContainer}>
<Imagesource={basketIcon}style={styles.icon}/>
</View>
<Viewstyle={styles.info}>
<Textstyle={styles.items}>{record.items}Items</Text>
<Textstyle={styles.address}>{record.address}</Text>
</View>
<Viewstyle={styles.total}>
<Textstyle={styles.date}>{record.date}</Text>
<Textstyle={styles.price}>${record.total}</Text>
</View>
</View>
);
8. OncewehavetheJSXdefined,it'stimetoaddthestyles.First,we'lldefine
colors,margins,paddings,andsoonforthemaincontainer,title,androw
container.Inordertocreatethethreecolumnsforeachrow,weneedtouse
theflexDirection:'row'property.We'lllearnmoreaboutthispropertyina
laterrecipeinthischapter:
conststyles=StyleSheet.create({
mainContainer:{
flex:1,
backgroundColor:'#fff',
},
title:{
backgroundColor:'#0f1b29',
color:'#fff',
fontSize:18,
fontWeight:'bold',
padding:10,
paddingTop:40,
textAlign:'center',
},
row:{
borderColor:'#f1f1f1',
borderBottomWidth:1,
flexDirection:'row',
marginLeft:10,
marginRight:10,
paddingTop:20,
paddingBottom:20,
},
});
9. Ifwerefreshthesimulators,weshouldseesomethingsimilartothe
followingscreenshot:
10. Now,insidetheStyleSheetdefinition,let'saddstylesfortheicon.We're
goingtoaddayellowcircleasthebackgroundandchangethecolorofthe
icontowhite:
iconContainer:{
alignItems:'center',
backgroundColor:'#feb401',
borderColor:'#feaf12',
borderRadius:25,
borderWidth:1,
justifyContent:'center',
height:50,
width:50,
},
icon:{
tintColor:'#fff',
height:22,
width:22,
},
11. Afterthischange,we'llseeaniceiconontheleftsideofeachrow,as
showninthefollowingscreenshot:
12. Finally,we'lladdthestylesforthetext.Weneedtosetcolor,size,fontWeight,
padding,andafewotherproperties:
info:{
flex:1,
paddingLeft:25,
paddingRight:25,
},
items:{
fontWeight:'bold',
fontSize:16,
marginBottom:5,
},
address:{
color:'#ccc',
fontSize:14,
},
total:{
width:80,
},
date:{
fontSize:12,
marginBottom:5,
},
price:{
color:'#1cad61',
fontSize:25,
fontWeight:'bold',
}
13. Theendresultshouldlooksimilartothefollowingscreenshot:
Howitworks...
Instep5,wecreatedthedatasourceandaddeddatatothestate.The
ListView.DataSourceclassimplementsperformancedataprocessingfortheListView
component.TherowHasChangedpropertyisrequired,anditshouldbeafunctionto
comparethenextelement.
Whenfillingupthedatasourcewithdata,weneedtocallthecloneWithRows
methodandsendanarrayofrecords.
Ifwewanttoaddmoredata,weshouldcallthecloneWithRowsmethodagainwith
anarraycontainingthepreviousandnewdata.Thedatasourcewillmakesureto
computethedifferencesandre-renderthelistasnecessary.
Instep7,wedefinetheJSXtorenderthelist.Onlytwopropertiesarerequired
forthelist:thedatasourcewealreadyhavefromstep6andrenderRow.
TherenderRowpropertyacceptsafunctionasavalue.Thisfunctionneedstoreturn
theJSXforeachrow.
There'smore...
We'vecreatedasimplelayoutusingflexbox;however,there'sanotherrecipein
thischapterwherewe'lldiveintomoredetailaboutusingflexbox.
Oncewehaveourlist,chancesarethatwe'regoingtoneedtoseethedetailof
eachorder.YoucanusetheTouchableHighlightcomponentasthemaincontainer
foreachrow,sogoaheadandgiveitatry.Ifyouarenotsurehowtousethe
TouchableHighlightcomponent,takealookattheCreatingatogglebuttonrecipe
fromearlierinthischapter.
Usingflexboxtocreatealayout
Inthisrecipe,we'lllearnaboutflexbox.Inthepreviousrecipesinthischapter,
we'vebeenusingflexboxtocreatelayouts,butinthisrecipe,we'llfocusonthe
propertieswehaveatourdisposalbyrecreatingthelayoutfromarandomname
generatorapplicationontheAppStorecalledNominazer(https://itunes.apple.com/
us/app/nominazer/id765422087?mt=8).
WorkinginflexboxinReactNativeisessentiallythesameasworkingwith
flexboxinCSS.Thismeansifyou'recomfortabledevelopingwebsiteswitha
flexboxlayout,thenyoualreadyknowhowtocreatelayoutsinReactNative!
ThisexercisewillcoverthebasicsofworkingwithflexboxinReactNative,but
foralistofallofthelayoutpropsyoucanuse,refertothedocumentationon
LayoutProps(https://facebook.github.io/react-native/docs/layout-props.html).
Gettingready
Let'sbeginbycreatinganewblankapp.We'llnameitflexbox-layout.
Howtodoit...
1. InApp.js,let'simportthedependencieswe'llneedforourapp:
importReactfrom'react';
import{StyleSheet,Text,View}from'react-native';
2. Ourapplicationonlyneedsarendermethodsincewe'rebuildingastatic
layout.TherenderedlayoutconsistsofacontainerViewelementandthree
childViewelementsforeachcoloredsectionoftheapp:
exportdefaultclassAppextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
<Viewstyle={styles.topSection}></View>
<Viewstyle={styles.middleSection}></View>
<Viewstyle={styles.bottomSection}></View>
</View>);
}
}
3. Next,wecanbeginaddingourstyles.Thefirststylewe'lladdwillbe
appliedtotheViewelementthatwrapsourentireapp.Settingtheflex
propertyto1willcauseallchildrenelementstofillallemptyspace:
conststyles=StyleSheet.create({
container:{
flex:1,
}
});
4. Now,wecanaddthestylesforthethreechildViewelements.Eachsection
hasaflexGrowpropertyappliedtoit,whichdictateshowmuchofthe
availablespaceeachelementshouldtakeup.topSectionandbottomSectionare
bothsetto3,sothey'lltakeupthesameamountofspace.Sincethe
middleSectionhastheflexGrowpropertysetto1,thiselementwilltakeupone
thirdofthespacethattopSectionandbottomSectiontakeup:
topSection:{
flexGrow:3,
backgroundColor:'#5BC2C1',
},
middleSection:{
flexGrow:1,
backgroundColor:'#FFF',
},
bottomSection:{
flexGrow:3,
backgroundColor:'#FD909E',
},
5. Ifweopenourapplicationinthesimulators,weshouldalreadybeableto
seethebasiclayouttakingshape:
6. Here,wecanaddaTextelementtoeachofthethreechildViewelementswe
createdinstep2.Notethenewlyaddedcodehasbeenhighlighted:
render(){
return(
<Viewstyle={styles.container}>
<Viewstyle={styles.topSection}>
<Textstyle={styles.topSectionText}>
4NAMES
</Text>
</View>
<Viewstyle={styles.middleSection}>
<Textstyle={styles.middleSectionText}>
IPSUM
</Text>
</View>
<Viewstyle={styles.bottomSection}>
<Textstyle={styles.bottomSectionText}>
COM
</Text>
</View>
</View>
);
}
7. Thetextforeachsectiondefaultstothetop-leftcornerofthatsection.We
canuseflexboxtojustifyandaligneachoftheseelementstothedesired
positions.AllthreechildViewelementshavethealignItemsflexpropertyset
to'center',whichwillcausethechildrenofeachelementtobe
centeredalongthexaxis.justifyContentisusedonthemiddleandbottom
sectionstodefinehowchildelementsshouldbejustifiedalongtheyaxis:
onststyles=StyleSheet.create({
container:{
flex:1,
},
topSection:{
flexGrow:3,
backgroundColor:'#5BC2C1',
alignItems:'center',
},
middleSection:{
flexGrow:1,
backgroundColor:'#FFF',
justifyContent:'center',
alignItems:'center',
},
bottomSection:{
flexGrow:3,
backgroundColor:'#FD909E',
alignItems:'center',
justifyContent:'flex-end'
}
});
8. Allthat'slefttobedoneistoaddbasicstylestotheTextelementsto
increasefontSize,fontWeight,andtherequiredmargin:
topSectionText:{
fontWeight:'bold',
marginTop:50
},
middleSectionText:{
fontSize:30,
fontWeight:'bold'
},
bottomSectionText:{
fontWeight:'bold',
marginBottom:30
}
9. Ifweopenourapplicationinsimulators,weshouldbeabletoseeour
completedlayout:
Howitworks...
Ourapplicationislookingreallygood,anditwasquiteeasytoaccomplishby
usingflexbox.WecreatedthreedistinctsectionsbyusingViewelementsthattake
updifferentfractionsofthescreenbysettingtheflexGrowpropertiesto3,1,and3,
respectively.Thiscausesthetopandbottomsectionstobeofequalverticalsize,
andthemiddlesectiontobeonethirdthesizeofthetopandbottom.
Whenusingflexbox,wehavetwodirectionstolayoutchildcontent,rowand
column:
row:Thisallowsustoarrangethechildrenofthecontainerhorizontally.
column:Thisallowsustoarrangethechildrenofthecontainervertically.This
isthedefaultdirectioninReactNative.
Whensettingflex:1aswedidwiththecontainerViewelement,we'retellingthat
elementtotakeupallavailablespace.Ifweweretoremoveflex:1orsetflexto
0,wecanseethelayoutcollapseinonitself,sincethecontainerisnolonger
flexingintoalloftheemptyspace:
Flexboxisgreatforsupportingdifferentscreenresolutionsaswell.Eventhough
differentdevicesmayhavedifferentresolutions,wecanensureconsistent
layoutsthatwilllookgoodonanydevice.
There'smore...
TherearesomedifferencesbetweenhowflexboxworksinReactNativeand
howitworksinCSS.First,thedefaultflexDirectionpropertyinCSSisrow,
whereasthedefaultflexDirectionpropertyinReactNativeiscolumn.
TheflexpropertyalsobehavesabitdifferentlyinReactNative.Insteadof
settingflextoastringvalue,itcanbesettoapositiveinteger,0,or-1.Asthe
officialReactNativedocumentationstates:
Whenflexisapositivenumber,itmakesthecomponentflexibleandit'llbesizedproportionaltoitsflex
value.So,acomponentwithflexsetto2willtaketwicethespaceasacomponentwithflexsetto1.When
flexis0,thecomponentissizedaccordingtowidthandheightandisinflexible.Whenflexis-1,the
componentisnormallysizedaccordingwidthandheight.However,ifthere'snotenoughspace,the
componentwillshrinktoitsminWidthandminHeight.
There'salotmoretotalkaboutflexbox,butfornowwe'vegottenourfeetwet.
InChapter3,ImplementingComplexUserInterfaces–PartI,we'lllearnmore
aboutlayouts.We'llcreateacomplexlayouttousemoreoftheavailablelayout
properties.
Seealso
ReactNativeLayoutPropsdocumentation(https://facebook.github.io/react-na
tive/docs/layout-props.html)
ReactNativeTextStylePropsdocumentation(https://facebook.github.io/react
-native/docs/text-style-props.html)
Yoga(https://github.com/facebook/yoga)—Facebook'sFlexboximplementation
utilizedbyReactNative
AnexcellentStackOverflowpostthatcovershowReactNativeflex
propertieswork,withexamples—https://stackoverflow.com/questions/43143258/fl
ex-vs-flexgrow-vs-flexshrink-vs-flexbasis-in-react-native
Settingupandusingnavigation
Foranyapplicationthathasmorethanoneview,anavigationsystemisof
paramountimportance.Theneedfornavigationissopervasiveinapplication
developmentthatExpoprovidestwotemplateswhenyoucreateanew
application:BlankorTabNavigation.Thisrecipeisbasedonaverypared
downversionoftheTabNavigationapptemplateprovidedbyExpo.We'llstill
begintherecipewithaBlankappandbuildourbasicTabNavigationappfrom
scratchtobetterunderstandalloftherequisiteparts.Aftercompletingthis
recipe,IencourageyoutostartanewappwiththeTabNavigationtemplateto
seesomeofthemoreadvancedfeatureswe'llbecoveringinlaterchapters,
includingpushnotificationsandstacknavigation.
Gettingready
Let'sgoaheadandcreateanewblankapplicationnamedsimple-navigation.We're
alsogoingtoneedathird-partypackageforhandlingournavigation.We'llbe
usingthereact-navigationpackage.IntheTerminal,navigatetotherootofthe
newprojectandinstallthispackagewiththefollowingcommand:
yarnaddreact-navigation
That'sallofthesetupweneed.Let'sbuild!
Howtodoit...
1. InsidetheApp.jsfile,let'simportourdependencies:
importReactfrom'react';
import{StyleSheet,View}from'react-native';
2. TheAppcomponentforthisappwillbeverysimple.WejustneedanApp
classwitharenderfunctionthatrendersourappcontainer.We'llalsoadd
stylesforfillingthewindowandaddingawhitebackground:
exportdefaultclassAppextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
</View>
);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
}
});
3. ThenextstepforApp.jswillbetoimportandusetheMainTabNavigator
component,whichisanewcomponentthatwe'llcreateinstep4:
importMainTabNavigatorfrom'./navigation/MainTabNavigator';
exportdefaultclassAppextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
<MainTabNavigator/>
</View>
);
}
}
4. We'llneedtocreateanewfileforourMainTabNavigatorcomponent.Let's
createanewfolderintherootoftheprojectcallednavigation.Inthisnew
folder,we'llcreateMainTabNavigator.jsforournavigationcomponent.
5. InMainTabNavigator.js,wecanimportallofthedependenciesweneedfor
navigation.Thedependenciesincludethreescreens(HomeScreen,LinksScreen,
andSettingsScreen).We'lladdthesescreensinlatersteps:
importReactfrom'react';
import{Ionicons}from'@expo/vector-icons';
import{TabNavigator,TabBarBottom}from'react-navigation';
importHomeScreenfrom'../screens/HomeScreen';
importLinksScreenfrom'../screens/LinksScreen';
importSettingsScreenfrom'../screens/SettingsScreen';
6. OurnavigationcomponentwillusetheTabNavigatormethodprovidedby
react-navigationfordefiningtheroutesandnavigationforour
app.TabNavigatortakestwoparameters:aRouteConfigobjecttodefineeach
routeandaTabNavigatorConfigobjecttodefinetheoptionsforourTabNavigator
component:
exportdefaultTabNavigator({
//RouteConfig,definedinstep7.
},{
//TabNavigatorConfig,definedinsteps8and9.
});
7. First,we'lldefinetheRouteConfigobject,whichwillcreatearoutemapfor
ourapplication.EachkeyintheRouteConfigobjectservesasthenameofthe
route.Wesetthescreenpropertyforeachroutetothecorrespondingscreen
componentwewanttobedisplayedonthatroute:
exportdefaultTabNavigator({
Home:{
screen:HomeScreen,
},
Links:{
screen:LinksScreen,
},
Settings:{
screen:SettingsScreen,
},
},{
//TabNavigatorConfig,definedinsteps8and9.
});
8. TabNavigatorConfighasalittlemoretoit.WepasstheTabBarBottomcomponent
providedbyreact-navigationtothetabBarComponentpropertytodeclarewhat
kindoftabbarwewanttouse(inthiscase,atabbardesignedforthe
bottomofthescreen).tabBarPositiondefineswhetherthebarisonthetopor
bottomofthescreen.animationEnabledspecifieswhethertransitionsare
animated,andswipeEnableddeclareswhetherviewscanbechangedvia
swiping:
exportdefaultTabNavigator({
//RouteConfig,definedinstep7.
},{
navigationOptions:({navigation})=>({
//navigationOptions,definedinstep9.
}),
tabBarComponent:TabBarBottom,
tabBarPosition:'bottom',
animationEnabled:false,
swipeEnabled:false,
});
9. InthenavigationOptionspropertyoftheTabNavigatorConfigobject,we'lldefine
dynamicnavigationOptionsforeachroutebydeclaringafunctionthattakes
thenavigationpropforthecurrentroute/screen.Wecanusethisfunctionto
decidehowthetabbarwillbehaveperroute/screen,sinceit'sdesignedto
returnanobjectthatsetsnavigationOptionsfortheappropriatescreen.We'll
usethispatterntodefinetheappearanceofthetabBarIconpropertyforeach
route:
navigationOptions:({navigation})=>({
tabBarIcon:({focused})=>{
//Definedinstep10
},
}),
10. ThetabBarIconpropertyissettoafunctionwhoseparametersaretheprops
forthecurrentroute.We'llusethefocusedproptodecidewhethertorendera
colorediniconoranoutlinedicon,dependingonthecurrentroute.We
getrouteNamefromthenavigationpropvianavigation.state,defineiconsfor
eachofourthreeroutes,andreturntherenderediconfortheappropriate
route.We'llusetheIoniconscomponentprovidedbyExpotocreateeach
iconanddefinetheicon'scolorbasedonwhethertheicon'srouteisfocused:
navigationOptions:({navigation})=>({
tabBarIcon:({focused})=>{
const{routeName}=navigation.state;
leticonName;
switch(routeName){
case'Home':
iconName=`ios-information-circle`;
break;
case'Links':
iconName=`ios-link`;
break;
case'Settings':
iconName=`ios-options`;
}
return(
<Ioniconsname={iconName}
size={28}style={{marginBottom:-3}}
color={focused?Colors.tabIconSelected:
Colors.tabIconDefault}
/>
);
},
}),
11. ThelaststepinsettingupMainTabNavigatoristocreatetheColorsconstantused
tocoloreachicon:
constColors={
tabIconDefault:'#ccc',
tabIconSelected:'#2f95dc',
}
12. Ourroutingisnowcomplete!Allthat'sleftnowistocreatethethreescreen
componentsforeachofthethreeroutesweimportedanddefined
inMainTabNavigator.js.Forsimplicity'ssake,eachofthethreescreenswill
haveidenticalcode,exceptforbackgroundcolorandidentifyingtext.
13. Intherootoftheproject,weneedtocreateascreensfoldertohouseour
threescreens.Inthenewfolder,we'llneedto
makeHomeScreen.js,LinksScreen.js,andSettingsScreen.js.
14. Let'sstartbyopeningthenewlycreatedHomeScreen.jsandaddingthe
necessarydependencies:
importReactfrom'react';
import{
StyleSheet,
Text,
View,
}from'react-native';
15. TheHomeScreencomponentitselfisquitesimple,justafullcolorpagewith
thewordHomeinthemiddleofthescreentoshowwhichscreenwe're
currentlyon:
exportdefaultclassHomeScreenextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.headline}>
Home
</Text>
</View>
);
}
}
16. We'llalsoneedtoaddthestylesforourHomescreenlayout:
conststyles=StyleSheet.create({
container:{
flex:1,
alignItems:'center',
justifyContent:'center',
backgroundColor:'#608FA0',
},
headline:{
fontWeight:'bold',
fontSize:30,
color:'white',
}
});
17. Allthat'sleftnowistorepeatstep14,step15,andstep16fortheremaining
twoscreens,alongwithsomeminorchanges.LinksScreen.jsshouldlooklike
HomeScreen.jswiththefollowinghighlightedsectionsupdated:
importReactfrom'react';
import{
StyleSheet,
Text,
View,
}from'react-native';
exportdefaultclassLinksScreenextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.headline}>
Links
</Text>
</View>
);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
alignItems:'center',
justifyContent:'center',
backgroundColor:'#F8759D',
},
headline:{
fontWeight:'bold',
fontSize:30,
color:'white',
}
});
18. Similarly,insideSettingsScreen.js,wecancreatethethirdscreencomponent
usingthesamestructureastheprevioustwoscreens:
importReactfrom'react';
import{
StyleSheet,
Text,
View,
}from'react-native';
exportdefaultclassSettingsScreenextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.headline}>
Settings
</Text>
</View>
);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
alignItems:'center',
justifyContent:'center',
backgroundColor:'#F0642E',
},
headline:{
fontWeight:'bold',
fontSize:30,
color:'white',
}
});
19. Ourapplicationiscomplete!Whenweviewourapplicationinthe
simulator,itshouldhaveatabbaralongthebottomofthescreenthat
transitionsbetweenourthreeroutes:
Howitworks...
Inthisrecipe,wecoveredoneofthemostcommonandfundamentalnavigation
patternsinnativeapps,thetabbar.TheReactNavigationlibraryisaveryrobust,
featurerichnavigationsolutionandwilllikelybeabletoprovideyourappwith
anykindofnavigationneeded.We'llcovermoreusesofReactNavigationinChap
ter3,ImplementingComplexUserInterfaces.
Seealso
ReactNavigationofficialdocumentation(https://reactnavigation.org/)
Expo'sguideonroutingandnavigation(https://docs.expo.io/versions/latest/gu
ides/routing-and-navigation.html)
ImplementingComplexUser
Interfaces-PartI
Inthischapter,wewillimplementcomplexuserinterfaces.Wewilllearnmore
aboutusingflexboxtocreatecomponentsthatworkondifferentscreensizes,
howtodetectorientationchanges,andmore.
Thechapterwillcoverthefollowingrecipes:
Creatingareusablebuttonwiththemesupport
Buildingacomplexlayoutfortabletsusingflexbox
Includingcustomfonts
Usingfonticons
Creatingareusablebuttonwith
themesupport
Reusabilityisveryimportantwhendevelopingsoftware.Weshouldavoid
repeatingthesamethingoverandoveragain,andinsteadweshouldcreatesmall
componentsthatwecanreuseasmanytimesasneeded.
Inthisrecipe,wewillcreateaButtoncomponent,andwearealsogoingtodefine
severalpropertiestochangeitslookandfeel.Whilegoingthroughthisrecipe,
wewilllearnhowtousepropertiesandhowtodynamicallyapplydifferent
stylestoacomponent.
Gettingready
Weneedtocreateanemptyapp.Let'snameitreusable-button.
Howtodoit...
1. Intherootofournewapp,we'llneedtocreateanewButtonfolderforour
reusablebutton-relatedcode.Let'salsocreateindex.jsandstyles.jsinour
newButtonfolder.
2. Wewillstartbyimportingthedependenciesforournewcomponent.Inthe
Button/index.jsfile,wewillbecreatingaButtoncomponent.Thismeanswe'll
needtoimporttheTextandTouchableOpacitycomponents.You'llnoticewe're
alsoimportingstylesthatdonotexistyet.Wewilldefinethesestylesina
differentfilelaterinthisrecipe.IntheButton/index.jsfile,weshouldhave
theseimports:
importReact,{Component}from'react';
import{
Text,
TouchableOpacity,
}from'react-native';
import{
Base,
Default,
Danger,
Info,
Success
}from'./styles';
3. Nowthatwehaveourdependenciesimported,let'sdefinetheclassforthis
component.Wearegoingtoneedsomepropertiesandtwomethods.It's
alsorequiredthatweexportthiscomponentsowecanuseitelsewhere:
exportdefaultclassButtonextendsComponent{
getTheme(){
//Definedinalaterstep
}
render(){
//Definedinalaterstep
}
}
4. Weneedtoselectthestylestoapplytoourcomponentbasedonthegiven
properties.Forthispurpose,wewilldefinethegetThememethod.Thismethod
willcheckwhetheranyofthepropertiesaretrueandwillreturnthe
appropriatestyles.Ifnonearetrue,itwillreturntheDefaultstyle:
getTheme(){
const{danger,info,success}=this.properties;
if(info){
returnInfo;
}
if(success){
returnSuccess;
}
if(danger){
returnDanger;
}
returnDefault;
}
5. It'srequiredthatallcomponentshavearendermethod.Here,weneedto
returntheJSXelementsforthiscomponent.Inthiscase,wewillgetthe
stylesforthegivenpropertiesandapplythemtotheTouchableOpacity
component.
Wearealsodefiningalabelforthebutton.Insidethislabel,wewillrender
thechildrenproperty.Ifacallbackfunctionisreceived,thenitwillbe
executedwhentheuserpressesthiscomponent:
render(){
consttheme=this.getTheme();
const{
children,
onPress,
style,
rounded,
}=this.properties;
return(
<TouchableOpacity
activeOpacity={0.8}
style={[
Base.main,
theme.main,
rounded?Base.rounded:null,
style,
]}
onPress={onPress}
>
<Textstyle={[Base.label,theme.label]}>{children}</Text>
</TouchableOpacity>
);
}
6. WearealmostdonewithourButtoncomponent.Westillneedtodefineour
styles,butfirstlet'smoveovertotheApp.jsfileintherootoftheproject.We
needtoimportthedependencies,includingtheButtoncomponentwehave
created.
Wearegoingtodisplayanalertmessagewhentheuserclicksthebutton,
therefore,wealsoneedtoimporttheAlertcomponent:
importReactfrom'react';
import{
Alert,
StyleSheet,
View
}from'react-native';
importButtonfrom'./Button';
7. Oncewehaveallthedependencies,let'sdefineastatelesscomponentthat
rendersafewbuttons.Thefirstbuttonwillusethedefaulttheme,andthe
secondbuttonwillusethesuccessstyle,whichwilladdanicegreencolor
tothebutton'sbackground.Thelastbuttonwilldisplayanalertwhenitgets
pressed.Forthat,weneedtodefinethecallbackfunctionthatwillusethe
Alertcomponent,justsettingthetitleandmessage:
exportdefaultclassAppextendsReact.Component{
handleButtonPress(){
Alert.alert('Alert','Youclickedthisbutton!');
}
render(){
return(
<Viewstyle={styles.container}>
<Buttonstyle={styles.btn}>
Myfirstbutton
</Button>
<Buttonsuccessstyle={styles.btn}>
Successbutton
</Button>
<Buttoninfostyle={styles.btn}>
Infobutton
</Button>
<Buttondangerroundedstyle={styles.btn}
onPress={this.handleButtonPress}>
Roundedbutton
</Button>
</View>
);
}
}
8. Wearegoingtoaddsomestylesforhowthemainlayoutshouldalignand
justifyeachbutton,alongwithsomemargins:
conststyles=StyleSheet.create({
container:{
flex:1,
alignItems:'center',
justifyContent:'center',
},
btn:{
margin:10,
},
});
9. Ifwetrytoruntheappnow,wewillgetsomeerrors.Thisisbecausewe
haven'tdeclaredthestylesforourbutton.Let'sworkonthatnow.Insidethe
Button/styles.jsfile,weneedtodefinethebasestyles.Thesestyleswillbe
appliedtoeveryinstanceofthebutton.Here,wewilldefinearadius,
padding,fontcolor,andallthecommonstylesthatweneedforthis
component:
import{StyleSheet}from'react-native';
constBase=StyleSheet.create({
main:{
padding:10,
borderRadius:3,
},
label:{
color:'#fff',
},
rounded:{
borderRadius:20,
},
});
10. Oncewehavethecommonstylesforourbutton,weneedtodefinethe
stylesfortheDanger,Info,Success,andDefaultthemes.Forthis,wearegoing
todefinedifferentobjectsforeachtheme.Insideeachtheme,wewilluse
thesameobjectbutwithspecificstylesforthattheme.
Tokeepthingssimple,weareonlygoingtochangethebackgroundColor,but
wedohavetheoptiontouseasmanystylepropertiesaswewant:
constDanger=StyleSheet.create({
main:{
backgroundColor:'#e74c3c',
},
});
constInfo=StyleSheet.create({
main:{
backgroundColor:'#3498db',
},
});
constSuccess=StyleSheet.create({
main:{
backgroundColor:'#1abc9c',
},
});
constDefault=StyleSheet.create({
main:{
backgroundColor:'rgba(0,0,0,0)',
},
label:{
color:'#333',
},
});
11. Finally,let'sexportthestyles.ThisstepisnecessarysothattheButton
componentcanimportallthestylesforeachtheme:
export{
Base,
Danger,
Info,
Success,
Default,
};
12. Ifweopentheapp,weshouldbeabletoseeourcompletedlayout:
Howitworks...
Inthisexample,wemadeuseoftheTouchableOpacitycomponent.Thiscomponent
allowsustodefineaniceanimationthatchangestheopacitywhentheuser
pressesthebutton.
WecanusetheactiveOpacitypropertytosettheopacityvaluewhenthebutton
getspressed.Thevaluecanbeanynumberbetween0and1,where0is
completelytransparent.
Ifwepresstheroundedbutton,wewillseeanativeAlertmessage,asshownin
thefollowingscreenshot:
Buildingacomplexlayoutfortablets
usingflexbox
Flexboxisreallyconvenientwhenitcomestocreatingresponsivelayouts.React
Nativeusesflexboxasalayoutsystem,andifyouarealreadyfamiliarwiththese
concepts,itwillbereallyeasyforyoutostartcreatinglayoutsofanykind.
Asdiscussedinthepreviouschapter,therearesomedifferencesbetweenthe
wayflexboxworksinReactNativeascomparedtohowitworksinCSS.For
moreinformationonthedifferencesbetweenReactNativeandCSSflexbox,
pleaserefertotheHowitworks...sectionoftheUsingflexboxtocreatea
layoutrecipeinChapter2,CreatingaSimpleReactNativeApp.
Inthisrecipe,wewillcreatealayouttodisplayalistofblogposts.Eachpost
willbeasmallcardwithanimage,anexcerpt,andabuttontoreadmore.We
willuseflexboxtoarrangethepostsonthemaincontainerbasedonscreensize.
Thiswillallowustohandlethescreenrotationbyproperlyaligningthecardsin
bothlandscapeandportrait.
Gettingready
Inordertofollowthestepsinthisrecipe,itisnecessarytocreateanemptyapp
usingtheReactNativeCLI.Wearegoingtonamethenewapptablet-flexbox.
WhenwecreateanewappwithExpo,thereisanapp.jsonthatgetscreatedatthe
baseoftheprojectthatprovidessomebasicconfiguration.Inthisrecipe,weare
buildinganappthatwewanttobesurelooksgoodonatablet,particularlyin
landscapemode.Whenweopenapp.json,weshouldseeanorientationproperty
setto'portrait'.Thispropertydetermineswhichorientationsshouldbeallowed
withinourapp.Theorientationpropertyaccepts'portrait'(lockapptoportrait
mode),'landscape'(lockapptolandscapemode),and'default'(allowappto
adjustscreenorientationbasedonthedevice'sorientation).Forourapp,wewill
settheorientationto'landscape'sothatwecansupportbothlandscapeandportrait
layouts.
We'llalsobeusingsomeimages,whichneedtobehostedremotelyforthis
recipetoproperlysimulateloadingremotedataanddisplayingimageswith
theImagecomponent.Ihaveuploadedtheseimagestothewww.imgur.comimage
hostingservice,andreferencedtheseremoteimagesinthedata.jsonfilethatthe
recipeusesforitsconsumabledata.If,foranyreason,theseremoteimagesdon't
loadproperlyforyou,theyarealsoinincludedintherepositoryforthisrecipe,
underthe/assetsfolder.Feelfreetouploadthemtoanyserverorhostingservice,
andupdatetheimageURLsindata.jsonaccordingly.Therepositorycanbefound
onGitHubathttps://github.com/warlyware/react-native-cookbook/tree/master/chapter-3/ta
blet-flexbox.
Howtodoit...
1. First,weneedtocreateaPostfolderintherootoftheproject.Weneedto
alsocreateanindex.jsandastyles.jsfileinthenewPostfolder.Wewilluse
thisPostcomponenttodisplayeachpostforourapp.Finally,weneedtoadd
adata.jsonfiletotherootoftheproject,whichwewillusetodefinealistof
posts.
2. NowwecanmoveontobuildingtheApp.jscomponent.First,weneedto
importthedependenciesforthisclass.WearegoingtouseaListView
componenttorenderthelistofposts.We'llalsoneedTextand
Viewcomponentsforcontentcontainers.WearegoingtocreateacustomPost
componenttorendereachpostonthelist;wealsoneedtoimportthe
data.jsonfile:
importReact,{Component}from'react';
import{ListView,StyleSheet,Text,View}from'react-native';
importPostfrom'./Post';
importdatafrom'./data.json';
3. Let'screatetheclassfortheAppcomponent.Here,wewillusethedatafrom
the.jsonfiletocreatethedataSourceforthelist.Wewilladdsomeactualdata
toourdata.jsonfileinthenextstep.Intherendermethod,wearegoingto
defineasimpletoptoolbarandtheListcomponent.Wearegoingtousethe
PostcomponentforeveryrecordandgetthedataSourcefromthestate.
IfyouhaveanyquestionsregardingtheListViewcomponent,youshould
takealookattherecipeinChapter2,CreatingaSimpleReactNativeApp,
wherewecreatedalistoforders:
constdataSource=newListView.DataSource({
rowHasChanged:(r1,r2)=>r1!==r2,
});
exportdefaultclassAppextendsComponent{
state={
dataSource:dataSouce.cloneWithRows(data.posts),
};
render(){
return(
<Viewstyle={styles.container}>
<Viewstyle={styles.toolbar}>
<Textstyle={styles.title}>Latestposts</Text>
</View>
<ListView
dataSource={this.state.dataSource}
renderRow={post=><Post{...post}/>}
style={styles.list}
contentContainerStyle={styles.content}
/>
</View>
);
}
}
4. Twofilesarestillmissing:the.jsonfilewiththedataandthePost
component.Inthisstep,wewillcreatethedatathatwearegoingtousefor
eachpost.Tomakethingssimple,thereisonlyonerecordofdatainthe
followingcodesnippet,buttherestofthePOSTobjectIusedinthisrecipe
canbefoundinthedata.jsonfileofthecoderepositoryforthisrecipe:
{
"posts":[
{
"title":"TheBestArticleEverWritten",
"img":"https://i.imgur.com/mf9daCT.jpg",
"content":"Loremipsumdolorsitamet...",
"author":"BobLabla"
},
//Addmorerecordshere.
]
}
5. Nowthatwehavesomedata,wearereadytoworkonthePostcomponent.
Inthiscomponent,weneedtodisplaytheimage,title,andbutton.Since
thiscomponentdoesnotneedtoknowaboutstate,wewilluseastateless
component.ThefollowingcodeusesallthecomponentsweknowfromChap
ter2,CreatingaSimpleReactNativeApp.Ifsomethingisunclear,please
reviewthatchapteragain.
Thiscomponentreceivesthedataasaparameter,whichwethenusefor
displayingthecontentinthecomponent.TheImagecomponentwilluse
theimgpropertydefinedoneachobjectinthedata.jsonfiletodisplaythe
remoteimage:
importReactfrom'react';
import{
Image,
Text,
TouchableOpacity,
View
}from'react-native';
importstylesfrom'./styles';
constPost=({content,img,title})=>(
<Viewstyle={styles.main}>
<Image
source={{uri:img}}
style={styles.image}
/>
<Viewstyle={styles.content}>
<Textstyle={styles.title}>{title}</Text>
<Text>{content}</Text>
</View>
<TouchableOpacitystyle={styles.button}activeOpacity={0.8}>
<Textstyle={styles.buttonText}>Readmore</Text>
</TouchableOpacity>
</View>
);
exportdefaultPost;
6. Oncewehavedefinedthecomponent,wealsoneedtodefinethestylesfor
eachpost.Let'screateanemptyStyleSheetexportsothatthePostcomponent
relyingonstyles.jswillproperlyfunction:
import{StyleSheet}from'react-native';
conststyles=StyleSheet.create({
//Definedinlatersteps
});
exportdefaultstyles;
7. Ifwetrytoruntheapp,weshouldbeabletoseethedatafromthe.jsonfile
onthescreen.Itwon'tbeveryprettythough,since,wehaven'tappliedany
stylesyet:
8. Wehaveeverythingweneedonthescreen.Nowwearereadytostart
workingonthelayout.First,let'saddstylesforourPostcontainer.We'llbe
settingwidth,height,borderRadius,andafewothers.Let'saddthemto
the/Post/styles.jsfile:
conststyles=StyleSheet.create({
main:{
backgroundColor:'#fff',
borderRadius:3,
height:340,
margin:5,
width:240,
}
});
9. Bynow,weshouldseesmallboxesverticallyaligned.That'ssome
progress,butweneedtoaddmorestylestotheimagesowecanseeit
onscreen.Let'saddanimagepropertytothesamestylesconstfromthelast
step.TheresizeModepropertywillallowustosethowwewanttoresizethe
image.Inthiscase,byselectingcover,theimagewillkeeptheaspectratio
oftheoriginal:
image:{
backgroundColor:'#ccc',
height:120,
resizeMode:'cover',
}
10. Forthecontentofthepost,wewanttotakealltheavailableheightonthe
card,thereforeweneedtomakeitflexibleandaddsomepadding.We'll
alsoaddoverflow:hiddentothecontenttoavoidoverflowingtheViewelement.
Forthetitle,weonlyneedtochangethefontSizeandaddamargintothe
bottom:
content:{
padding:10,
overflow:'hidden',
flex:1,
},
title:{
fontSize:18,
marginBottom:5,
},
11. Finally,forthebutton,wewillsetthebackgroundColortogreenandthetextto
white.Wealsoneedtoaddsomepaddingandmarginforspacing:
button:{
backgroundColor:'#1abc9c',
borderRadius:3,
padding:10,
margin:10,
},
buttonText:{
color:'#fff',
textAlign:'center',
}
12. Ifwerefreshthesimulator,weshouldseeourpostsinsmallcards.
Currently,thecardsarearrangedvertically,butwewanttorenderallof
themhorizontally.Wearegoingtofixthatinthefollowingsteps:
Primarystyleshavebeenaddedforallpostelements
13. Currently,wecanonlyseethefirstthreeitemsonthelistinacolumn,
insteadofinarowacrossthescreen.Let'sreturntotheApp.jsfileandstart
addingourstyles.Weaddflex:1tothecontainersothatourlayoutwill
alwaysfillthescreen.Wealsowanttoshowatoolbaratthetop.Forthat,
wejustneedtodefinesomepaddingandcolorasfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
},
toolbar:{
backgroundColor:'#34495e',
padding:10,
paddingTop:20,
},
title:{
color:'#fff',
fontSize:20,
textAlign:'center',
}
});
14. Let'saddsomebasicstylestothelistaswell.Justanicebackgroundcolor
andsomepadding.We'llalsoaddtheflexproperty,whichwillensurethe
listtakesalltheavailableheightonthescreen.Weonlyhavetwo
componentshere:thetoolbarandthelist.Thetoolbaristakingabout50px.
Ifwemakethelistflexible,itwilltakealloftheremainingavailablespace,
whichisexactlywhatwewantwhenrotatingthedeviceorwhenrunning
theappindifferentscreenresolutions:
list:{
backgroundColor:'#f0f3f4',
flex:1,
paddingTop:5,
paddingBottom:5,
}
15. Ifwechecktheappinthesimulatoroncemore,weshouldbeabletosee
thetoolbarandlistbeinglaidoutasexpected:
Stylesappliedtoeachposttogivethemacardlikeappearance
16. Wearealmostdonewiththisapp.Allwehavelefttodoistoarrangethe
cardshorizontally.Thiscanbeachievedwithflexboxinthreesimplesteps:
content:{
flexDirection:'row',
flexWrap:'wrap',
justifyContent:'space-around',
},
ThefirststepisapplyingthesecontentstylesviathecontentContainerStyle
propertyintheListViewcomponent.Internally,theListViewcomponentwill
applythesestylestothecontentcontainer,whichwrapsallofthechild
views.
WethensettheflexDirectiontorow.Thiswillhorizontallyalignthecards
onthelist;however,thispresentsanewproblem:wecanonlyseeone
singlerowofposts.Tofixtheproblem,weneedtowraptheitems.We
dothisbysettingtheflexWrappropertytowrap,whichwillautomatically
movetheitemsthatdon'tfitintheviewtothenextrow.Lastly,weuse
thejustifyContentpropertyandsetittocenter,whichwillcenterour
ListViewinthemiddleofourapp.
17. Wenowhavearesponsiveappthatlooksgoodonatabletinlandscape
mode:
Side-by-sidecomparisonofiPadandAndroidtabletscreenshotsinlandscapemode
Andlooksjustasgoodinportraitmode:
Side-by-sidecomparisonofiPadandAndroidtabletscreenshotsinportraitmode
There'smore...
ExpoalsoprovidesaScreenOrientationhelperforchangingtheorientation
configurationoftheapp.Thishelperalsoallowsformoregranularorientation
settings(suchasALL_BUT_UPSIDE_DOWNorLANDSCAPE_RIGHT).Ifyourappneedsdynamic,
granularcontroloverscreenorientation,seetheScreenOrientationExpo
documentationforinformation:https://docs.expo.io/versions/v24.0.0/sdk/screen-orien
tation.html.
Seealso
Officialdocumentationonstaticimageresourcesandthe<Image>componentcan
befoundathttps://facebook.github.io/react-native/docs/images.html.
Includingcustomfonts
Atsomepoint,weareprobablygoingtowanttodisplaytextwithacustomfont
family.Untilnow,we'vebeenusingthedefaultfont,butwecanuseanyother
thatwelike.
BeforeExpo,theprocessofaddingcustomfontswasmoredifficult,required
workingwithnativecode,andneededtobeimplementeddifferentlyiniOSand
Android.Luckily,throughtheuseofExpo'sfonthelperlibrary,thishasbecome
streamlinedandsimplified
Inthisrecipe,wewillimportafewfontsandthendisplaytextusingeachofthe
importedfontfamilies.Wewillalsousedifferentfontstyles,suchasboldand
italic.
Gettingready
Inordertoworkonthisexample,weneedsomefonts.Youcanusewhatever
fontsyouwant.IrecommendgoingtoGoogleFonts(https://fonts.google.com/)
anddownloadingyourfavorites.Forthisrecipe,wewillbeusingtheJosefin
SansandRalewayfonts.
Onceyouhavethefontsdownloaded,let'screateanemptyappandnameit
custom-fonts.WhenwecreateablankappwithExpo,itcreatesanassetsfolderin
therootoftheprojectforplacingallofyourassets(images,fonts,andsoon),so
we'llfollowthestandardandaddourfontstothisfolder.Let'screatethe
/assets/fontsfolderandaddourcustomfontfilesdownloadedfromGoogleFonts.
WhendownloadingfontsfromGoogleFonts,you'llgeta.zipfilecontaininga
.ttffileforeachofthefontfamilyvariants.Wewillbeusingtheregular,bold,
anditalicvariations,socopythecorresponding.ttffilesforeachvariantineach
familytoour/assets/fontsfolder.
Howtodoit...
1. Withourfontfilesinplace,thefirststepistoopenApp.jsandtheimports
we'llneed:
importReactfrom'react';
import{Text,View,StyleSheet}from'react-native';
import{Font}from'expo';
2. Next,we'lladdasimplecomponentfordisplayingsometextthatwewant
tostylewithourcustomfonts.We'llstartwithjustoneTextelementto
displaytheregularvariantoftheRobotofont:
exportdefaultclassAppextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.josefinSans}>
Hello,JosefinSans!
</Text>
</View>
);
}
}
3. Let'salsoaddsomestarterstylesforthecomponentwe'vejustcreated.For
now,we'lljustincreasethefontsizeforourjosefinSansclassstyles:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'center',
},
josefinSans:{
fontSize:40,
}
});
4. Ifweopentheappnowinoursimulator,wewillseetheHello,Josefin
Sans!textdisplayedinthemiddleofthescreenusingthedefaultfont:
5. Let'sloadourJosefinSans-Regular.ttffontfilesothatwecanstyleourtext
withit.We'llusethecomponentDidMountlifecyclehookprovidedbyReact
Nativetotellourappwhentostartloadingthefont:
exportdefaultclassAppextendsReact.Component{
componentDidMount(){
Font.loadAsync({
'josefin-sans-regular':require('./assets/fonts/JosefinSans-Regular.ttf'),
});
}
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.josefinSans}>
Hello,JosefinSans!
</Text>
</View>
);
}
}
6. Next,we'lladdthefontwe'reloadingtothestylesbeingappliedtoourText
element:
conststyles=StyleSheet.create({
//Otherstylesfromstep3
josefinSans:{
fontSize:40,
fontFamily:'josefin-sans-regular'
}
});
7. Boom,Wenowhavestyles,right?Well,notquite.Ifwelookbackatour
simulators,we'llseethatwe'regettinganerrorinstead:
console.error:"fontFamily'josefin-sans-regular'isnotasystemfontandhasnotbeenloadedthroughExpo.Font.loadAsync"
8. ButwedidjustloadfontsviaExpo.Font.loadAsync!Whatgives?Itturnsoutwe
havearaceconditiononourhands.ThejosefinSansstyleswedefinedforour
TextelementarebeingappliedbeforetheJosefinSansfonthasbeenloaded.
Tohandlethisproblem,willneedtousethecomponent'sstatetokeeptrack
oftheloadstatusofthefont:
exportdefaultclassAppextendsReact.Component{
state={
fontLoaded:false
};
9. Nowthatourcomponenthasastate,wecanupdatethestate'sfontLoaded
propertytotrueoncethefontisloaded.UsingtheES6featureasync/await
makesthissuccinctandstraightforward.Let'sdothisinourcomponentDidMount
codeblock:
asynccomponentDidMount(){
awaitFont.loadAsync({
'josefin-sans-regular':require('./assets/fonts/JosefinSans-
Regular.ttf'),
});
}
10. SincewearenowawaitingtheFont.loadAsync()call,wecansetthestateof
fontLoadedtotrueoncethecalliscomplete:
asynccomponentDidMount(){
awaitFont.loadAsync({
'josefin-sans-regular':require('./assets/fonts/JosefinSans-
Regular.ttf'),
});
this.setState({fontLoaded:true});
}
11. Allthat'slefttodoistoupdateourrendermethodtoonlyrendertheText
elementdependingonthecustomfontwhenthefontLoadedstatepropertyis
true:
<Viewstyle={styles.container}>
{
this.state.fontLoaded?(
<Textstyle={styles.josefinSans}>
Hello,JosefinSans!
</Text>
):null
}
</View>
12. Now,whenwecheckoutourappinthesimulators,weshouldseeour
customfontbeingapplied:
13. Let'sloadtherestofourfontssothatwecanusetheminourappaswell:
awaitFont.loadAsync({
'josefin-sans-regular':require('./assets/fonts/JosefinSans-
Regular.ttf'),
'josefin-sans-bold':require('./assets/fonts/JosefinSans-
Bold.ttf'),
'josefin-sans-italic':require('./assets/fonts/JosefinSans-
Italic.ttf'),
'raleway-regular':require('./assets/fonts/Raleway-
Regular.ttf'),
'raleway-bold':require('./assets/fonts/Raleway-Bold.ttf'),
'raleway-italic':require('./assets/fonts/Raleway-
Italic.ttf'),
});
14. We'llalsoneedTextelementsfordisplayingtextineachofournewfont
families/variants.Notethatwe'llalsoneedtowrapallourTextelementsin
anotherViewelement,sinceJSXexpressionsrequirethattherebeonlyone
parentnode.We'realsonowpassingthestylepropertyanarrayofstylesto
applyinordertoconsolidatethefontSizeandpaddingstyleswe'llbeapplying
inthenextstep:
render(){
return(
<Viewstyle={styles.container}>
{
this.state.fontLoaded?(
<Viewstyle={styles.container}>
<Textstyle={[styles.josefinSans,
styles.textFormatting]}>
Hello,JosefinSans!
</Text>
<Textstyle={[styles.josefinSansBold,
styles.textFormatting]}>
Hello,JosefinSans!
</Text>
<Textstyle={[styles.josefinSansItalic,
styles.textFormatting]}>
Hello,JosefinSans!
</Text>
<Textstyle={[styles.raleway,styles.textFormatting]}>
Hello,Raleway!
</Text>
<Textstyle={[styles.ralewayBold,
styles.textFormatting]}>
Hello,Raleway!
</Text>
<Textstyle={[styles.ralewayItalic,
styles.textFormatting]}>
Hello,Raleway!
</Text>
</View>
):null
}
</View>
);
}
15. Allthat'slefttoapplyourcustomfontsistoaddthenewstylestothe
StyleSheet:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'center',
},
josefinSans:{
fontFamily:'josefin-sans-regular',
},
josefinSansBold:{
fontFamily:'josefin-sans-bold',
},
josefinSansItalic:{
fontFamily:'josefin-sans-italic',
},
raleway:{
fontFamily:'raleway-regular',
},
ralewayBold:{
fontFamily:'josefin-sans-bold'
},
ralewayItalic:{
fontFamily:'josefin-sans-italic',
},
textFormatting:{
fontSize:40,
paddingBottom:20
}
});
16. Now,inourapp,we'llseesixdifferenttextelements,eachstyledwithits
owncustomfont:
Howitworks...
Instep5andstep6,weusedthecomponentDidMountReactlifecyclehooktotellour
appwhentoload.WhileitmayseemtemptingtousecomponentWillMount,thistoo
willthrowanerror,sincecomponentWillMountisnotguaranteedtowaitforour
Font.loadAsynctofinish.ByusingcomponentDidMount,wecanalsoassurewearenot
blockingtheinitialrenderingoftheapp.
Instep9,weusedtheES6featureasync/await.You'relikelyfamiliarwiththis
patternifyou'reawebdeveloper,butifyou'dlikemoreinformation,I've
includedanawesomearticlefromponyfoo.comintheSeealsosectionattheendof
thisrecipe,whichdoesagreatjobofexplaininghowasync/awaitworks.
Instep11,weusedaternarystatementtorendereitherourcustomfontstyled
Textelementifloaded,ortorendernothingifit'snotloadedbyreturningnull.
FontsloadedthroughExpodon’tcurrentlysupportthefontWeightorfontStyleproperties—you
willneedtoloadthosevariationsofthefontandspecifythembyname,aswehavedonehere
withbold.
Seealso
Agreatarticleonasync/awaitcanbefoundathttps://ponyfoo.com/articles/understandin
g-javascript-async-await.
Usingfonticons
Iconsareanindispensablepartofalmostanyapp,particularlyinnavigationand
buttons.SimilartoExpo'sfonthelper,coveredinthepreviouschapter,Expoalso
hasaniconhelperthatmakesaddingiconfontsmuchlessofahasslethanusing
vanillaReactNative.Inthisrecipe,we'llseehowtousetheiconhelpermodule
withthepopularFontAwesomeandIoniconsiconfontlibraries.
Gettingready
We'llneedtomakeanewprojectforthisrecipe.Let'snamethisprojectfont-
icons.
Howtodoit...
1. We'llbeginbyopeningApp.jsandimportingthedependenciesthatweneed
tobuildtheapp:
importReactfrom'react';
import{StyleSheet,Text,View}from'react-native';
import{FontAwesome,Ionicons}from'@expo/vector-icons';
2. Next,wecanaddtheshelloftheapplication,wherewewilldisplaythe
icons:
exportdefaultclassAppextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
</View>
);
}
}
3. InsideoftheViewelement,let'saddtwomoreViewelementsforholdingicons
fromeachiconset:
exportdefaultclassAppextendsReact.Component{
render(){
return(
<Viewstyle={styles.container}>
<Viewstyle={styles.iconRow}>
</View>
<Viewstyle={styles.iconRow}>
</View>
</View>
);
}
}
4. Now,let'saddthestylesforeachofourdeclaredelements.Aswe'veseenin
previousrecipes,thecontainerstylesfillthescreenwithflex:1andcenter
theitemswithalignItemsandjustifyContentsettocenter.TheiconRowproperty
setstheflexDirectiontorowsothatouriconswillbelinedupinarow:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'center',
},
iconRow:{
flexDirection:'row',
},
});
5. Nowthatthebasicstructureofourappisinplace,let'saddouricons.Inthe
firstrowoficons,we'llusefourFontAwesomecomponentstodisplayfouricons
fromtheFontAwesomefontlibrary.Thenamepropertydetermineswhichicon
shouldbeused,thesizepropertysetsthesizeoftheiconinpixels,andthe
colorsetswhatcolortheiconshouldbe:
<Viewstyle={styles.iconRow}>
<FontAwesomestyle={styles.iconPadding}name="glass"size={48}color="green"/>
<FontAwesomestyle={styles.iconPadding}name="beer"size={48}color="red"/>
<FontAwesomestyle={styles.iconPadding}name="music"size={48}color="blue"/>
<FontAwesomestyle={styles.iconPadding}name="taxi"size={48}color="#1CB5AD"/>
</View>
JustasinCSS,thecolorpropertycanbeacolorkeyworddefinedintheCSSspecification(you
cancheckoutthefulllistintheMDNdocsathttps://developer.mozilla.org/en-
US/docs/Web/CSS/color_value),orahexcodeforagivencolor.
6. InthenexticonrowViewelement,we'lladdiconsfromtheIoniconsfont
library.Asyoucansee,theIoniconselementtakesthesamepropertiesasthe
FontAwesomeelementsusedinthepreviousstep:
<Viewstyle={styles.iconRow}>
<Ioniconsstyle={styles.iconPadding}name="md-pizza"size={48}color="orange"/>
<Ioniconsstyle={styles.iconPadding}name="md-tennisball"size={48}color="maroon"/>
<Ioniconsstyle={styles.iconPadding}name="ios-thunderstorm-outline"size={48}color="purple"/>
<Ioniconsstyle={styles.iconPadding}name="ios-happy-outline"size={48}color="#DF7977"/>
</View>
7. Thelaststepinthisrecipeistoaddtheremainingstyle,iconPadding,which
justaddssomepaddingtoevenlyspaceouteachofouricons:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'center',
},
iconRow:{
flexDirection:'row',
},
iconPadding:{
padding:8,
}
});
8. That'sallittakes!Whenwecheckoutourapp,therewillbetworowsof
icons,eachrowshowcasingiconsfromFontAwesomeandIoniconsrespectively:
Howitworks...
Thevector-iconspackagethatcomeswithExpoprovidesaccessto11fullicon
sets.Allyouhavetodoisimporttheassociatedcomponent(forexample,the
FontAwesomecomponentforFontAwesomeicons)andprovideitwiththenamethat
correspondstotheiconinthesetthatyou'dliketouse.Youcanfindafull,
searchablelistofalltheiconsyoucanusewiththevector-iconshelperlibraryin
thevector-iconsdirectory,hostedathttps://expo.github.io/vector-icons/.Simplyset
theelement'snamepropertytotheiconnamelistedinthedirectory,addsizeand
colorproperties,andyou'redone!
AstheGitHubREADMEforvector-iconsstates,thislibraryisacompatibility
layercreatedforusingtheiconsprovidedbythereact-native-vector-iconspackage
inExpo.Youcanfindthispackageathttps://github.com/oblador/react-native-vector-i
cons.IfyouarebuildingaReactNativeappwithoutExpo,youcangetthesame
functionalitybyusingthereact-native-vector-iconslibraryinstead.
Seealso
Acatalogofalloftheiconsavailableinthevector-iconslibrarycanbefoundatht
tps://expo.github.io/vector-icons/.
ImplementingComplexUser
Interfaces-PartII
ThischapterwillcovermorerecipesonbuildingUIswithReactNative.We'll
getourfirstlookatlinkingtootherapplicationsandwebsites,handlingachange
indeviceorientation,andhowtobuildaformforcollectinguserinput.
Inthischapter,wewillcoverthefollowingrecipes:
Dealingwithuniversalapplications
Detectingorientationchanges
UsingaWebViewtoembedexternalwebsites
Linkingtowebsitesandotherapplications
Creatingaformcomponent
Dealingwithuniversalapplications
OneofthebenefitsofusingReactNativeisitsabilitytoeasily
createuniversalapplications.Wecansharealotofcodebetweenphoneand
tabletapplications.Thelayoutsmightchange,dependingonthedevice,butwe
canreusepiecesofcodeforbothtypesofdeviceacrosslayouts.
Inthisrecipe,wewillbuildanappthatrunsonphonesandtablets.Thetablet
versionwillincludeadifferentlayout,butwewillreusethesameinternal
components.
Gettingready
Forthisrecipe,wewillshowalistofcontacts.Fornow,wewillloadthedata
froma.jsonfile.Wewillexplorehowtoloadremotedatafroma
RepresentationalStateTransfer(REST)APIinalaterchapter.
Let'sopenthefollowingURLandcopythegeneratedJSONtoafile
calleddata.jsonattherootoftheproject.Wewillusethisdatatorenderthelistof
contacts.ItreturnsaJSONobjectoffakeuserdataathttp://api.randomuser.me/?resu
lts=20.
Let'screateanewappcalleduniversal-app.
Howtodoit...
1. Let'sopenApp.jsandimportthedependencieswe'llneedinthisapp,aswell
asourdata.jsonfilewecreatedinthepreviousGettingreadysection.We'll
alsoimportaDeviceutilityfrom./utils/Device,whichwewillbuildinalater
step:
importReact,{Component}from'react';
import{StyleSheet,View,Text}from'react-native';
importDevicefrom'./utils/Device';
importdatafrom'./data.json';
2. Here,we'regoingtocreatethemainAppcomponentanditsbasiclayout.
Thistop-levelcomponentwilldecidewhethertorenderthephoneortablet
UI.WeareonlyrenderingtwoTextelements.TherenderDetailtextshouldbe
displayedontabletsonlyandtherenderMastertextshouldbedisplayedon
phonesandtablets:
exportdefaultclassAppextendsComponent{
renderMaster(){
return(
<Text>Renderonphoneandtablets!!</Text>
);
}
renderDetail(){
if(Device.isTablet()){
return(
<Text>Renderontabletsonly!!</Text>
);
}
}
render(){
return(
<Viewstyle={styles.content}>
{this.renderMaster()}
{this.renderDetail()}
</View>
);
}
}
3. UndertheAppcomponent,we'lladdafewbasicstyles.Thestyles
temporarilyincludepaddingTop:40sothatourrenderedtextisnotoverlapped
bythedevice'ssystembar:
conststyles=StyleSheet.create({
content:{
paddingTop:40,
flex:1,
flexDirection:'row',
},
});
4. Ifwetrytorunourappasitis,itwillfailwithanerrortellingusthat
theDevicemodulecannotbefound,solet'screateit.Thepurposeofthis
utilityclassistocalculatewhetherthecurrentdeviceisaphoneortablet,
basedonthescreendimensions.ItwillhaveanisTabletmethodand
anisPhonemethod.Weneedtocreateautilsfolderintherootoftheproject
andaddaDevice.jsfortheutility.Nowwecanaddthebasicstructureofthe
utility:
import{Dimensions,Alert}from'react-native';
//Tabletportraitdimensions
consttablet={
width:552,
height:960,
};
classDevice{
//Addedinnextsteps
}
constdevice=newDevice();
exportdefaultdevice;
5. Let'sstartbuildingouttheutilitybycreatingtwomethods:onetogetthe
dimensionsinportraitandtheothertogetthedimensionsinlandscape.
Dependingonthedevicerotation,thevaluesofwidthandheightwillchange,
whichiswhyweneedthesetwomethodstoalwaysgetthecorrectvalues,
whetherthedeviceislandscapeorportrait:
classDevice{
getPortraitDimensions(){
const{width,height}=Dimensions.get("window");
return{
width:Math.min(width,height),
height:Math.max(width,height),
};
}
getLandscapeDimensions(){
const{width,height}=Dimensions.get("window");
return{
width:Math.max(width,height),
height:Math.min(width,height),
};
}
}
6. Nowlet'screatethetwomethodsourappwillusetodeterminewhetherthe
appisrunningonatabletoraphone.Tocalculatethis,weneedtogetthe
dimensionsinportraitmodeandcomparethemwiththedimensionswe
havedefinedforatablet:
isPhone(){
constdimension=this.getPortraitDimensions();
returndimension.height<tablet.height;
}
isTablet(){
constdimension=this.getPortraitDimensions();
returndimension.height>=tablet.height;
}
7. Now,ifweopentheapp,weshouldseetwodifferenttextsbeingrendered,
dependingonwhetherwe'rerunningtheapponaphoneoratablet:
8. Theutilityworksasexpected!Let'sreturntoworkingontherenderMaster
methodofthemainApp.js.Wewantthismethodtorenderthelistof
contactsthatliveinthedata.jsonfile.Let'simportanewcomponent,which
we'llbuildoutinthefollowingsteps,andupdatetherenderMastermethodto
useournewcomponent:
importUserListfrom'./UserList';
exportdefaultclassAppextendsComponent{
renderMaster(){
return(
<UserListcontacts={data.results}/>
);
}

//...
}
9. Let'screateanewUserListfolder.Insidethisfolder,weneedtocreate
theindex.jsandstyles.jsfilesforthenewcomponent.Thefirstthingwe
needtodoisimportthedependenciesintothenewindex.js,createthe
UserListclass,andexportitasthedefault:
importReact,{Component}from'react';
import{
StyleSheet,
View,
Text,
ListView,
Image,
TouchableOpacity,
}from'react-native';
importstylesfrom'./styles';
exportdefaultclassUserListextendsComponent{
//Definedinthefollowingsteps
}
10. We'vealreadycoveredhowtocreatealist.Ifyouarenotclearonhowthe
ListViewcomponentworks,readtheDisplayingalistofitemsrecipeinChapte
r2,CreatingaSimpleReactNativeApp.Intheconstructoroftheclass,we
willcreatethedataSourceandthenaddittothestate:
exportdefaultclassUserListextendsComponent{
constructor(properties){
super(properties);
constdataSource=newListView.DataSource({
rowHasChanged:(r1,r2)=>r1!==r2
});
this.state={
dataSource:dataSource.cloneWithRows(properties.contacts),
};
}

//...
}
11. TherendermethodalsofollowsthesamepatternintroducedintheListView
recipe,Displayingalistofitems,fromChapter2,CreatingaSimpleReact
NativeApp:
render(){
return(
<Viewstyle={styles.main}>
<Textstyle={styles.toolbar}>
Mycontacts!
</Text>
<ListViewdataSource={this.state.dataSource}
renderRow={this.renderContact}
style={styles.main}/>
</View>);
}
12. Asyoucansee,weneedtodefinetherenderContactmethodtorendereachof
therows.WeareusingtheTouchableOpacitycomponentasthemainwrapper,
whichwillallowustouseacallbackfunctiontoperformsomeactions
whenalistitemispressed.Fornow,wearenotdoinganythingwhenthe
buttonispressed.Wewilllearnmoreaboutcommunicatingbetween
componentsusingReduxinfuturechapters:
renderContact=(contact)=>{
return(
<TouchableOpacitystyle={styles.row}>
<Imagesource={{uri:`${contact.picture.large}`}}style=
{styles.img}/>
<Viewstyle={styles.info}>
<Textstyle={styles.name}>
{this.capitalize(contact.name.first)}
{this.capitalize(contact.name.last)}
</Text>
<Textstyle={styles.phone}>{contact.phone}</Text>
</View>
</TouchableOpacity>
);
}
13. Wedon'thaveawaytocapitalizethetextsusingstyles,soweneedtouse
JavaScriptforthat.Thecapitalizefunctionisquitesimple,andsetsthefirst
letterofthegivenstringtouppercase:
capitalize(value){
returnvalue[0].toUpperCase()+value.substring(1);
}
14. Wearealmostdonewiththiscomponent.Allthat'sleftarethestyles.Let's
openthe/UserList/styles.jsfileandaddstylesforthemaincontainerand
thetoolbar:
import{StyleSheet}from'react-native';
exportdefaultStyleSheet.create({
main:{
flex:1,
backgroundColor:'#dde6e9',
},
toolbar:{
backgroundColor:'#2989dd',
color:'#fff',
paddingTop:50,
padding:20,
textAlign:'center',
fontSize:20,
},
//Remainingstylesaddedinnextstep.
});
15. Now,foreachrow,wewanttorendertheimageofeachcontactontheleft,
andthecontact'snameandphonenumberontheright:
row:{
flexDirection:'row',
padding:10,
},
img:{
width:70,
height:70,
borderRadius:35,
},
info:{
marginLeft:10,
},
name:{
color:'#333',
fontSize:22,
fontWeight:'bold',
},
phone:{
color:'#aaa',
fontSize:16,
},
16. Let'sswitchovertotheApp.jsfileandremovethepaddingToppropertywe
usedformakingtextlegibleinstep7;thelinetoberemovedisshownin
bold:
conststyles=StyleSheet.create({
content:{
paddingTop:40,
flex:1,
flexDirection:'row',
},
});
17. Ifwetrytorunourapp,weshouldbeabletoseeareallynicelistonthe
phoneaswellasthetablet,andthesamecomponentonthetwodifferent
devices:
18. Wearealreadydisplayingtwodifferentlayoutsbasedonthecurrentdevice!
NowweneedtoworkontheUserDetailview,whichwillshowtheselected
contact.Let'sopenApp.js,importtheUserDetailviews,andupdate
therenderDetailmethod,asfollows:
importUserDetailfrom'./UserDetail';
exportdefaultclassAppextendsComponent{
renderMaster(){
return(
<UserListcontacts={data.results}/>
);
}
renderDetail(){
if(Device.isTablet()){
return(
<UserDetailcontact={data.results[0]}/>
);
}
}
}
Asmentionedearlier,inthisrecipe,wearenotfocusingonsendingdatafromonecomponent
toanother,butinsteadonrenderingadifferentlayoutintabletsandphones.Therefore,we
willalwayssendthefirstrecordtotheuserdetailsviewforthisrecipe.
19. Tomakethingssimpleandtomaketherecipeasshortaspossible,forthe
userdetailsview,wewillonlydisplayatoolbarandsometextshowingthe
firstandlastnameofthegivenrecord.Wearegoingtouseastateless
componenthere:
importReactfrom'react';
import{
View,
Text,
}from'react-native';
importstylesfrom'./styles';
constUserList=({contact})=>(
<Viewstyle={styles.main}>
<Textstyle={styles.toolbar}>Detailsshouldgohere!</Text>
<Text>
Thisisthedetailview:{contact.name.first}{contact.name.last}
</Text>
</View>
);
exportdefaultUserList;
20. Finally,weneedtostylethiscomponent.Wewanttoassignthree-quarters
ofthescreentothedetailspageandone-quartertothemasterlist.Thiscan
bedoneeasilybyusingflexbox.SincetheUserListcomponenthasaflex
propertyof1,wecansettheflexpropertyofUserDetailto3,allowing
UserDetailtotakeup75%ofthescreen.Herearethestyleswe'lladdto
the/UserDetail/styles.jsfile:
import{StyleSheet}from'react-native';
conststyles=StyleSheet.create({
main:{
flex:3,
backgroundColor:'#f0f3f4',
},
toolbar:{
backgroundColor:'#2989dd',
color:'#fff',
paddingTop:50,
padding:20,
textAlign:'center',
fontSize:20,
},
});
exportdefaultstyles;
21. Ifwetrytorunourappagain,wewillseethatonthetablet,itwillrendera
nicelayoutshowingboththelistviewandthedetailview,whileonthe
phone,itonlyshowsthelistofcontacts:
Howitworks...
IntheDeviceutility,weimportedadependencythatReactNativeprovidescalled
Dimensionforgettingthedimensionsofthecurrentdevice.Thisiswhatweneedin
ordertofigureoutwhetherourappisrunningonaphoneoratablet.
WealsodefinedatabletconstantintheDeviceutility,whichisanobject
containingthewidthandheightthatisusedtocalculatewhetherthedeviceisa
tabletornot.ThevaluesofthisconstantarebasedonthesmallestAndroidtablet
availableonthemarket.
Instep5,wegotthewidthandheightbycalling
theDimensions.get("window")method,andthenwegotthemaximumandminimum
valuesdependingontheorientationwewanted.
Instep12,it'simportanttonotethatweusedanarrowfunctiontodefine
therenderContactmethod.Usinganarrowfunctionkeepsthecorrectbinding
scope,otherwise,thethisinthecalltothis.capitalizewouldbeboundtothe
wrongscope.ChecktheSeealsosectionformoreinformationonhowboththe
thiskeywordandarrowfunctionswork.
Seealso
AgoodexplanationofES6arrowfunctionsfromponyfooathttps://ponyfoo.c
om/articles/es6-arrow-functions-in-depth
Anin-depthlookathowthisworksinJavaScriptbyKyleSimpsonathttps:
//github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch
2.md
Detectingorientationchanges
Whenbuildingcomplexinterfaces,it'sverycommontorenderdifferentUI
components,basedonthedevice'sorientation.Thisisespecially
truewhendealingwithtablets.
Inthisrecipe,wewillrenderamenubasedonscreenorientation.Inlandscape,
wewillrenderanexpandedmenuwithiconsandtexts,andinportrait,wewill
onlyrendertheicons.
Gettingready
Tosupportorientationchanges,wearegoingtouseExpo'shelperutilitycalled
ScreenOrientation.
WewillalsousetheFontAwesomecomponentprovidedbytheExpo
package@expo/vector-icons.TheUsingfonticonsrecipeinChapter2,Creatinga
SimpleReactNativeApp,describeshowtousethiscomponent.
Beforewegetstarted,let'screateanewappcalledscreen-orientation.We'llalso
needtomakeatweaktotheapp.jsonfilethatExpocreatesintherootofthe
directory.ThisfilehasafewbasicsettingsExpouseswhenbuildingtheapp.
Oneofthesesettingsisorientation,whichisautomaticallysettoportraitforevery
newapp.Thissettingdeterminestheorientationstheappallows,andcanbeset
toportrait,landscape,ordefault.Ifwechangethistodefault,ourappwillallow
bothportraitandlandscapeorientations.
Toseethesechangestakeeffect,besuretorestartyourExpoproject.
Howtodoit...
1. We'llstartbyopeningApp.jsandaddingtheimportswe'llbeusing:
importReactfrom'react';
import{
Dimensions,
StyleSheet,
Text,
View
}from'react-native';
2. Next,we'lladdtheemptyAppclassforthecomponent,alongwithsome
basicstyles:
exportdefaultclassAppextendsReact.Component{
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff'
},
text:{
fontSize:40,
}
});
3. Withtheshellofourappinplace,wecannowaddtherendermethod.Inthe
rendermethod,you'llnoticewe'vegotaViewcomponentusingtheonLayout
property,whichwillfireoffwhenevertheorientationofthedevicechanges.
TheonLayoutwillthenrunthis.handleLayoutChange,whichwewilldefineinthe
nextstep.IntheTextelement,wesimplydisplaythevalueoforientationon
thestateobject:
exportdefaultclassAppextendsReact.Component{
render(){
return(
<View
onLayout={()=>this.handleLayoutChange}
style={styles.container}
>
<Textstyle={styles.text}>
{this.state.orientation}
</Text>
</View>
);
}
}
4. Let'screatethehandleLayoutChangemethodofourcomponent,aswellasthe
getOrientationfunctionthatthehandleLayoutChangemethodcalls.
ThegetOrientationfunctionusestheReactNativeDimensionsutilitytogetthe
widthandheightofthescreen.Ifheight>width,weknowthatthedeviceis
inportraitorientation,andifnot,thatitisinlandscapeorientation.By
updatingstate,are-renderwillbeinitiated,andthevalueof
this.state.orientationwillreflecttheorientation:
handleLayoutChange(){
this.getOrientation();
}
getOrientation(){
const{width,height}=Dimensions.get('window');
constorientation=height>width?'Portrait':'Landscape';
this.setState({
orientation
});
}
5. Ifweruntheappatthispoint,we'llgettheerrorTypeError:nullisnotan
object:(evaluating'this.state.orientation').Thishappensbecausetherender
methodisattemptingtoreadfromthethis.state.orientationvaluebeforeit's
evenbeendefined.Wecaneasilyfixthisproblembygettingtheorientation
beforerenderrunsforthefirsttime,viatheReactlifecycle
componentWillMounthook:
componentWillMount(){
this.getOrientation();
}
6. That'sallittakestogetthebasicfunctionalitywe'relookingfor!Runthe
appagainandyoushouldseethedisplayedtextreflecttheorientationofthe
device.Rotatethedevice,andtheorientationtextshouldupdate:
7. Nowthattheorientationstatevalueisupdatingproperly,wecanfocuson
theUI.Asmentionedbefore,wewillcreateamenuthatrenderstheoptions
slightlydifferentlybasedonthecurrentorientation.Let'simportaMenu
component,whichwe'llbuildoutinthenextsteps,andupdate
therendermethodofourAppcomponenttousethenewMenucomponent.
Noticethatwearenowpassingthis.state.orientationtotheorientation
propertyoftheMenucomponent:
importMenufrom'./Menu';
exportdefaultclassAppextendsReact.Component{
//...
render(){
return(
<View
onLayout={()=>{this.handleLayoutChange()}}
style={styles.container}
>
<Menuorientation={this.state.orientation}/>
<Viewstyle={styles.main}>
<Text>MainContent</Text>
</View>
</View>
);
}
}
8. Let'salsoupdatethestylesforourAppcomponent.Youcanreplacethe
stylesfromstep2withthefollowingcode.BysettingtheflexDirection
torowonthecontainerstyles,we'llbeabletodisplaythetwocomponents
horizontally:
conststyles=StyleSheet.create({
container:{
flex:1,
flexDirection:'row',
},
main:{
flex:1,
backgroundColor:'#ecf0f1',
justifyContent:'center',
alignItems:'center',
}
});
9. Next,let'sbuildouttheMenucomponent.We'llneedtocreatea
new/Menu/index.jsfile,whichwilldefinetheMenuclass.This
componentwillreceivetheorientationpropertyanddecidehowtorender
themenuoptionsbasedontheorientationvalue.Let'sstartbyimportingthe
dependenciesforthisclass:
importReact,{Component}from'react';
import{StyleSheet,View,Text}from'react-native';
import{FontAwesome}from'@expo/vector-icons';
10. NowwecandefinetheMenuclass.Onthestateobject,wewilldefinean
arrayofoptions.Theseoptionobjectswillbeusedtodefinetheicons.As
discussedintheUsingfonticonsrecipeinthepreviouschapter,wecan
defineiconsviakeywords,asdefinedinthevector-icondirectory,foundat
https://expo.github.io/vector-icons/:
exportdefaultclassMenuextendsComponent{
state={
options:[
{title:'Dashboard',icon:'dashboard'},
{title:'Inbox',icon:'inbox'},
{title:'Graphs',icon:'pie-chart'},
{title:'Search',icon:'search'},
{title:'Settings',icon:'gear'},
],
};
//Remainderdefinedinfollowingsteps
}
11. Therendermethodforthiscomponentloopsthroughthearrayofoptionsin
thestateobject:
render(){
return(
<Viewstyle={styles.content}>
{this.state.options.map(this.renderOption)}
</View>
);
}
12. Asyoucansee,insidetheJavaScriptXML(JSX)inthelaststep,there'sa
calltorenderOption.Inthismethod,wearegoingtorendertheiconandthe
labelforeachoption.We'llalsousetheorientationvaluetotoggleshowing
thelabel,andtochangetheicon'ssize:
renderOption=(option,index)=>{
constisLandscape=this.properties.orientation==='Landscape';
consttitle=isLandscape
?<Textstyle={styles.title}>{option.title}</Text>
:null;
consticonSize=isLandscape?27:35;
return(
<Viewkey={index}style={[styles.option,styles.landscape]}>
<FontAwesomename={option.icon}size={iconSize}color="#fff"/>
{title}
</View>
);
}
Inthepreviouscodeblock,noticethatwearedefiningakeyproperty.Whendynamically
creatinganewcomponent,wealwaysneedtosetakeyproperty.Thispropertyshouldbe
uniqueforeachitem,sinceit'susedinternallybyReact.Inthiscase,weareusingtheindexof
theloopiteration.Thisway,wecanbeassuredthateveryitemwillhaveauniquekeyvalue.
Youcanreadmoreaboutitintheofficialdocumentation:https://reactjs.org/docs/lists-and-keys.html.
13. Finally,we'lldefinethestylesforthemenu.First,wewillset
thebackgroundColortodarkblue,andthen,foreachoption,we'llchange
theflexDirectiontorendertheiconandlabelhorizontally.Therestofthe
stylesaddmarginsandpaddingssothatthemenuitemsarenicelyspaced
apart:
conststyles=StyleSheet.create({
content:{
backgroundColor:'#34495e',
paddingTop:50,
},
option:{
flexDirection:'row',
paddingBottom:15,
},
landscape:{
paddingRight:30,
paddingLeft:30,
},
title:{
color:'#fff',
fontSize:16,
margin:5,
marginLeft:20,
},
});
14. Ifwerunourapplicationnow,itwilldisplaythemenuUIdifferently
dependingontheorientationofthescreen.Rotatethedevice,andthelayout
willautomaticallyupdate:

There'smore...
Inthisrecipe,wehadourfirstlookattheapp.jsonfilethatexistsaspartofevery
Expoproject.Therearemanyusefulsettingsthatcanbeadjustedinthisfilethat
affectthebuildprocessoftheproject.Youcanusethisfiletoadjustorientation
lock,defineanappicon,andsetasplashscreen,amongmanyothersettings.
Youcanreviewallofthesettingssupportedbyapp.jsonintheExpoconfiguration
documentation,hostedathttps://docs.expo.io/versions/latest/guides/configuration.htm
l.
ExpoalsoprovidestheScreenOrientationutility,whichcanbeusedinsteadto
declaretheallowedorientationsforyourapp.Usingtheutility'smainmethod
ScreenOrientation.allow(orientation),willoverwritethecorrespondingsettingin
app.json.Theutilityalsoprovidesmoregranularoptionsthanthesettingin
app.json,suchasALL_BUT_UPSIDE_DOWNandLANDSCAPE_RIGHT.Formoreonthisutility,you
canreadthedocumentationathttps://docs.expo.io/versions/latest/sdk/screen-orientat
ion.html.
UsingaWebViewtoembedexternal
websites
Formanyapplications,it'srequiredthatexternallinkscanbevisitedand
displayedwithintheapp.Thiscanbeforshowingathird-partywebsite,online
help,andthetermsandconditionsofusingyourapp,amongotherthings.
Inthisrecipe,wewillseehowtoopenaWebViewbyclickingonabuttoninour
appanddynamicallysettingtheURLvalue.We'llalsobeusingthereact-
navigationpackagefortocreatebasicstacknavigationinthisrecipe.Pleasecheck
outtheSettingupandusingnavigationrecipeinChapter3,Implementing
ComplexUserInterfaces–PartIforadeeperdiveintobuildingnavigation.
Iftheneedsofyourapparebettermetbyloadingexternalwebsitesviathe
device'sbrowser,seethenextrecipe,Linkingtowebsitesandotherapplications.
Gettingready
WewillneedtocreateanewappforourWebView-basedrecipe.Let'snameour
newappweb-view.We'llalsobeusingreact-navigation,sobesuretoinstallthisas
well.Youcanuseyarnornpmtoinstallthepackage.Intherootoftheproject,run
thefollowing:
yarnaddreact-navigation
Alternatively,installthemusingnpm:
npminstall--savereact-navigation
Howtodoit...
1. Let'sstartbyopeningtheApp.jsfile.Inthisfile,we'llbeusingthe
StackNavigatorcomponentprovidedbythereact-navigationpackage.First,let's
addtheimportswe'llbeusinginthisfile.HomeScreenisacomponentwewill
bebuildinglaterinthisrecipe:
importReact,{Component}from'react';
import{StackNavigator}from'react-navigation';
importHomeScreenfrom'./HomeScreen';
2. Nowthatwehaveourimports,let'susetheStackNavigatorcomponentto
definethefirstroute;we'llbeusingaHomeroutewithlinksthatshouldbe
displayedusingtheReactNativeWebViewcomponent.ThenavigationOptions
propertyallowsustodefineatitletobedisplayedinthenavigationheader:
constApp=StackNavigator({
Home:{
screen:HomeScreen,
navigationOptions:({navigation})=>({
title:'Home'
}),
},
});
exportdefaultApp;
3. WearenowreadytocreatetheHomeScreencomponent.Let'screateanew
folderintherootofourproject,calledHomeScreenandaddanindex.jsfileto
thefolder.Asusual,wecanbeginwithourimports:
importReact,{Component}from'react';
import{
TouchableOpacity,
View,
Text,
SafeAreaView,
}from'react-native';
importstylesfrom'./styles';
4. NowwecandeclareourHomeScreencomponent.Let'salsoaddastateobject
tothecomponentwithalinksarray.Thisarrayhasanobjectforeachlink
we'llbeusinginthiscomponent.I'veprovidedfourlinksforyoutouse;
however,youcaneditthetitleandurlineachlinksarrayobjecttoany
websitesyou'dlike:
exportdefaultclassHomeScreenextendsComponent{
state={
links:[
{
title:'SmashingMagazine',
url:'https://www.smashingmagazine.com/articles/'
},
{
title:'CSSTricks',
url:'https://css-tricks.com/'
},
{
title:'GitconnectedBlog',
url:'https://medium.com/gitconnected'
},
{
title:'HackerNews',
url:'https://news.ycombinator.com/'
}
],
};
}
5. We'rereadytoaddarenderfunctiontothiscomponent.Here,weareusing
theSafeAreaViewforthecontainerelement.ThisworksjustlikeanormalView
element,butalsoaccountsforthenotchareaontheiPhoneXsothatnopart
ofourlayoutisobscuredbythedevicebezels.You'llnoticethatweare
usingmaptomapoverthelinksarrayfromthepreviousstep,passingeach
onetotherenderButtonfunction:
render(){
return(
<SafeAreaViewstyle={styles.container}>
<Viewstyle={styles.buttonList}>
{this.state.links.map(this.renderButton)}
</View>
</SafeAreaView>
);
}
6. Nowthatwehavedefinedtherendermethod,we'llneedtocreatethe
renderButtonmethodthatit'susing.Thismethodtakeseachlinkasa
parametercalledbutton,andtheindex,whichwe'lluseastheuniquekeyfor
eachelementrenderButtoniscreating.Formoreonthispoint,seetheTipin
step12ofthesecondrecipeinthischapter,Detectingorientationchanges.
TheTouchableOpacitybuttonelementwillfirethis.handleButtonPress(button)
whenpressed:
renderButton=(button,index)=>{
return(
<TouchableOpacity
key={index}
onPress={()=>this.handleButtonPress(button)}
style={styles.button}
>
<Textstyle={styles.text}>{button.title}</Text>
</TouchableOpacity>
);
}
7. NowweneedtocreatethehandleButtonPressmethodusedintheprevious
step.Thismethodusestheurlandtitlepropertiesfromthepassed-inbutton
parameter.Wecanthenusetheseinacallto
this.properties.navigation.navigate(),passinginthenameoftheroutewewant
tonavigatetoandtheparametersthatshouldbepassedalongtothatroute.
Wehaveaccesstoapropertycallednavigationbecauseweareusing
StackNavigator,whichwesetupinstep2:
handleButtonPress(button){
const{url,title}=button;
this.properties.navigation.navigate('Browser',{url,title});
}
8. TheHomeScreencomponentisdone,exceptforthestyles.Let'saddastyles.js
fileintheHomeScreenfoldertodefinethesestyles:
import{StyleSheet}from'react-native';
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
},
buttonList:{
flex:1,
justifyContent:'center',
},
button:{
margin:10,
backgroundColor:'#c0392b',
borderRadius:3,
padding:10,
paddingRight:30,
paddingLeft:30,
},
text:{
color:'#fff',
textAlign:'center',
},
});
exportdefaultstyles;
9. Now,ifweopentheapp,weshouldseetheHomeScreencomponentbeing
renderedwithourlistoffourlinkbuttons,andaheaderwiththetitleHome
renderedinthenativestyleoneachdevice.SincethereisnoBrowserroutein
ourStackNavigator,however,thebuttonswillnotactuallydoanythingwhen
pressed:
10. Let'sreturntotheApp.jsfileandaddtheBrowserroute.First,we'llneedto
importtheBrowserScreencomponent,whichwe'llcreateinthefollowing
steps:
importBrowserScreenfrom'./BrowserScreen';
11. NowthattheBrowserScreencomponenthasbeenimported,wecanadditto
theStackNavigatorobjecttocreateaBrowserroute.InnavigationOptions,we're
definingadynamictitlebasedontheparameterspassedtotheroute.These
parametersarethesameastheobjectwepassedintothe
navigation.navigate()callasthesecondargumentinstep7:
constApp=StackNavigator({
Home:{
screen:HomeScreen,
navigationOptions:({navigation})=>({
title:'Home'
}),
},
Browser:{
screen:BrowserScreen,
navigationOptions:({navigation})=>({
title:navigation.state.params.title
}),
},
});
12. WearereadytocreatetheBrowserScreencomponent.Let'screateanewfolder
intherootoftheprojectcalledBrowserScreenwithanewindex.jsfileinside,
thenaddtheimportsthiscomponentneeds:
importReact,{Component}from'react';
import{WebView}from'react-native';
13. TheBrowserScreencomponentisfairlysimple.Itconsistsonlyofarender
methodthatreadstheparamspropertyfromthenavigation.stateproperty
passedintocalltothethis.properties.navigation.navigatethatfireswhena
buttonispressed,asdefinedinstep7.Allweneedtodoisrenderthe
WebViewcomponentandsetitssourcepropertytoanobjectwiththeuri
propertysettoparams.url:
exportdefaultclassBrowserScreenextendsComponent{
render(){
const{params}=this.properties.navigation.state;
return(
<WebView
source={{uri:params.url}}
/>
);
}
}
14. Now,ifwegobacktotheapprunninginthesimulator,wecanseeour
WebViewinaction!
HackerNewsandSmashingMagazinevisitedfromourapp
Howitworks...
UsingaWebViewtoopenexternalsitesisagreatwaytoallowauserto
consumeexternalwebsiteswhilekeepingtheminourapp.Manyapplications
outtheredothis,allowingtheusertoreturntothemainportionoftheappeasily.
Instep6,weusedanarrowfunctiontobindthefunctionintheonPressproperty
tothescopeofthecurrentclassinstance,sinceweareusingthisfunctionwhen
loopingthroughthearrayoflinks.
Instep7,wheneverabuttonispressed,weusethetitleandURLthatarebound
tothatbutton,passingthemalongasparametersaswenavigatetotheBrowser
screen.ThenavigationOptionsinstep11usethissametitlevalueasthetitleofthe
screen.ThenavigationOptionstakeafunctionwhosefirstparameterisanobject
containingnavigation,whichprovidestheparametersusedwhennavigating.In
step11,westructurenavigationfromthisobjectsothatwecansettheview's
titletonavigation.state.params.title.
ThankstotheStackNavigatorcomponentprovidedbyreact-navigation,wegeta
headerwithOS-specificanimations,builtinwithabackbutton.Youcanread
theStackNavigationdocumentationformoreinformationonthiscomponentathttps
://reactnavigation.org/docs/stack-navigator.html.
Step13usestheURLpassedtotheBrowserScreencomponenttorenderaWebView
byusingtheURLintheWebView'ssourceproperty.Youcanfindalistofall
availableWebViewpropertiesintheofficialdocumentationlocatedathttps://face
book.github.io/react-native/docs/webview.html.
Linkingtowebsitesandother
applications
WehavelearnedhowtouseaWebViewtorenderathird-partywebsiteasan
embeddedpartofourapp.However,sometimes,wemightwanttousethenative
browsertoopenasite,linktoothernativesystemapplications(suchasemail,
phone,andSMS),orevendeeplinktoacompletelyseparateapp.
Inthisrecipe,wewilllinktoanexternalsiteviaboththenativebrowseranda
browsermodalwithinourapp,createlinkstothephoneandmessaging
applications,andcreateadeeplinkthatwillopentheSlackappand
automaticallyloadthe#generalchannelinthegitconnected.comSlackgroup.
Youwillneedtorunthisapponarealdeviceinordertoopenthelinksinthisappthatusethe
device'ssystemapplications,suchasemail,phone,andSMSlinks.Inmyexperience,thiswill
notworkinthesimulator.
Gettingready
Let'screateanewappforthisrecipe.We'llcallitlinking-app.
Howtodoit...
1. Let'sstartbyopeningApp.jsandaddingtheimportswe'llbeusing:
importReactfrom'react';
import{StyleSheet,Text,View,TouchableOpacity,Platform}from'react-native';
import{Linking}from'react-native';
import{WebBrowser}from'expo';
2. Next,let'saddbothanAppcomponentandastateobject.Inthisapp,the
stateobjectwillhouseallofthelinksthatwe'llbeusinginthisrecipeinan
arraycalledlinks.Noticehowtheurlpropertyineachlinksobjecthasa
protocolattachedtoit(tel,mailto,sms,andsoon).Theseprotocolsareused
bythedevicetoproperlyhandleeachlink:
exportdefaultclassAppextendsReact.Component{
state={
links:[
{
title:'CallSupport',
url:'tel:+12025550170',
type:'phone'
},
{
title:'EmailSupport',
url:'mailto:support@email.com',
type:'email',
},
{
title:'TextSupport',
url:'sms:+12025550170',
type:'textmessage',
},
{
title:'JoinusonSlack',
url:'slack://channel?team=T5KFMSASF&id=C5K142J57',
type:'slackdeeplink',
},
{
title:'VisitSite(internal)',
url:'https://google.com',
type:'internallink'
},
{
title:'VisitSite(external)',
url:'https://google.com',
type:'externallink'
}
]
}
}
ThephonenumberusedintheTextSupportandCallSupportbuttonsisanunusednumberat
thetimeofwriting,asgeneratedbyhttps://fakenumber.org/.Thisnumberislikelytostillbe
unused,butthiscouldpossiblychange.Feelfreetouseadifferentfakenumberfortheselinks,
justmakesuretokeeptheprotocolinplace.
3. Next,let'saddtherenderfunctionforourapp.TheJSXhereissimple:we
mapoverthestate.linksarrayfromthepreviousstep,passingeachtoour
renderButtonfunctiondefinedinthenextstep:
render(){
return(
<Viewstyle={styles.container}>
<Viewstyle={styles.buttonList}>
{this.state.links.map(this.renderButton)}
</View>
</View>
);
}
4. Let'sbuildouttherenderButtonmethodusedinthelaststep.Foreachlink,
wecreateabuttonwithTouchableOpacityandsettheonPresspropertyto
executethehandleButtonPressandpassitthebuttonproperty:
renderButton=(button,index)=>{
return(
<TouchableOpacity
key={index}
onPress={()=>this.handleButtonPress(button)}
style={styles.button}
>
<Textstyle={styles.text}>{button.title}</Text>
</TouchableOpacity>
);
}
5. Next,wecanbuildoutthehandleButtonPressfunction.Here,we'llbeusingthe
typepropertythatwe'veaddedtoeachobjectinthelinksarray.Ifthetype
is'internallink',wewanttoopentheURLwithinourappusingthe
ExpoWebBrowsercomponent'sopenBrowserAsyncmethod,andforeverythingelse,
we'llusetheReactNativeLinkingcomponent'sopenURLmethod.
Ifthere'saproblemwiththeopenURLcallandtheURLisusingtheslack://
protocol,itmeansthedevicedoesnotknowhowtohandletheprotocol,
probablybecausetheslackappisn'tinstalled.We'llhandlethisproblem
withthehandleMissingAppfunction,whichwe'lladdinthenextstep:
handleButtonPress(button){
if(button.type==='internallink'){
WebBrowser.openBrowserAsync(button.url);
}else{
Linking.openURL(button.url).catch(({message})=>{
if(message.includes('slack://')){
this.handleMissingApp();
}
});
}
}
6. NowwecancreateourhandleMissingAppfunction.Here,weusetheReact
NativehelperPlatform,whichprovidesinformationabouttheplatformthe
appisrunningon.Platform.OSwillalwaysreturntheoperatingsystem,
which,onphones,shouldalwaysresolvetoeither'ios'or'android'.Youcan
readmoreaboutthecapabilitiesofPlatformintheofficialdocumentationath
ttps://facebook.github.io/react-native/docs/platform-specific-code.html.
IfthelinktotheSlackappdoesnotworkasexpected,we'lluse
Linking.openURLagain;thistime,toopentheappintheappstoreappropriate
forthedevice:
handleMissingApp(){
if(Platform.OS==='ios'){
Linking.openURL(`https://itunes.apple.com/us/app/id618783545`);
}else{
Linking.openURL(
`https://play.google.com/store/applications/details?id=com.Slack`
);
}
}
7. Ourappdoesn'thaveanystylesyet,solet'saddsome.Nothingfancyhere,
justaligningthebuttonsinthecenterofthescreen,coloringandcentering
text,andprovidingpaddingoneachbutton:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
justifyContent:'center',
alignItems:'center',
},
buttonList:{
flex:1,
justifyContent:'center',
},
button:{
margin:10,
backgroundColor:'#c0392b',
borderRadius:3,
padding:10,
paddingRight:30,
paddingLeft:30,
},
text:{
color:'#fff',
textAlign:'center',
},
});
8. That'sallthereistothisapp.Onceweloadtheapp,thereshouldbea
columnofbuttonsrepresentingeachofourlinks.Notethatsomeofthese
linkswillonlybehaveproperlyonanactualdevice.Irecommendrunning
thisrecipeonarealdevicetoseewhetherallthelinksworkproperly:
Howitworks...
Instep2,wedefinedallthelinksthatourappuses.Eachlinkobjecthasatype
propertythatweuseinthehandleButtonPressmethoddefinedinstep5.
ThishandleButtonPressfunctionusesthelink'stypetodeterminewhichoneoftwo
strategieswillbeused.Ifthelink'stypeis'internallink',wewanttoopenthe
linkwiththedevicebrowserasamodalthatpopsupwithintheappitself.For
thispurpose,wecanuseExpo'sWebBrowserhelper,passingtheURLtoits
openBrowserAsyncmethod.Ifthelink'stypeis'externallink',we'llopenthelinkwith
ReactNative'sLinkinghelper.Thisletsyouseethedifferentwaysyoucanopena
websitefromyourapp.
TheLinkinghelpercanhandleprotocolsotherthanHTTPandHTTPSaswell.By
simplyusingtheproperprotocolinthelinkwepasstoLinking.openURL,wecan
openthetelephone(tel:),messaging(sms:),oremail(mailto:).
Linking.openURLcanalsohandledeeplinkstootherapplications,aslongastheapp
youwanttolinktohasaprotocolfordoingso,suchashowweopenSlackby
usingtheslack://protocol.FormoreinformationonSlack'sdeeplinking
protocolandwhatyoucandowithit,visittheirdocumentationathttps://api.slac
k.com/docs/deep-linking.
Instep5,wecatchanyerrorcausedbycallingLinking.openURL,checkwhetherthe
errorwascausedbytheSlackprotocolusingmessage.includes('slack://'),andifso,
weknowtheSlackappisnotinstalledonthedevice.Inthiscase,wefire
handleMissingApp,whichopenstheappstorelinkforSlackusingtheappropriate
link,asdeterminedbyPlatform.OS.
Seealso
OfficialdocumentationontheLinkingmodulecanbefoundathttps://docs.expo.io/
versions/latest/guides/linking.html.
Creatingaformcomponent
Mostapplicationsrequireawaytoinputdata,whetherit'sasimpleregistration
andloginformoramorecomplexcomponentwithmanyinputfieldsand
controls.
Inthisrecipe,wewillcreateaformcomponenttohandletextinputs.Wewill
collectdatausingdifferentkeyboards,andshowanalertmessagewiththe
resultinginformation.
Gettingready
Weneedtocreateanemptyapp.Let'snameituser-form.
Howtodoit...
1. Let'sstartbyopeningApp.jsandaddingourimports.Theimportsinclude
theUserFormcomponentthatwe'llbebuildingoutinalaterstep:
importReactfrom'react';
import{
Alert,
StyleSheet,
ScrollView,
SafeAreaView,
Text,
TextInput,
}from'react-native';
importUserFormfrom'./UserForm';
2. Sincethiscomponentisgoingtobeverysimple,wearegoingtocreatea
statelesscomponentforourApp.Wewillonlyrenderatoptoolbarinside
aScrollViewfortheUserFormcomponent:
constApp=()=>(
<SafeAreaViewstyle={styles.main}>
<Textstyle={styles.toolbar}>FitnessApp</Text>
<ScrollViewstyle={styles.content}>
<UserForm/>
</ScrollView>
</SafeAreaView>
);
conststyles=StyleSheet.create({
//Definedinalaterstep
});
exportdefaultApp;
3. Weneedtoaddsomestylestothesecomponents.We'lladdsomecolorsand
padding,aswellassettingthemainclasstoflex:1tofilltheremainderofthe
screen:
conststyles=StyleSheet.create({
main:{
flex:1,
backgroundColor:'#ecf0f1',
},
toolbar:{
backgroundColor:'#1abc9c',
padding:20,
color:'#fff',
fontSize:20,
},
content:{
padding:10,
},
});
4. WehavedefinedthemainAppcomponent.Nowlet'sgettoworkonthe
actualform.Let'screateanewdirectorycalledUserForminthebaseofthe
projectandaddanindex.jsfile.Then,we'llimportallthedependenciesfor
thisclass:
importReact,{Component}from'react';
import{
Alert,
StyleSheet,
View,
Text,
TextInput,
TouchableOpacity,
}from'react-native';
5. Thisistheclassthatwillrendertheinputsandkeeptrackofthedata.We
aregoingtosavethedataonthestateobject,sowe'llstartbyinitializing
stateasanemptyobject:
exportdefaultclassUserFormextendsComponent{
state={};
//Definedinalaterstep
}
conststyles=StyleSheet.create({
//Definedinalaterstep
});
6. Intherendermethod,wearegoingtodefinethecomponentsthatwewantto
display,whichinthiscasearethreetextinputsandabutton.Wearegoing
todefinearenderTextfieldmethodthatacceptsaconfigurationobjectasa
parameter.We'lldefinethenameofthefield,theplaceholder,and
thekeyboardtypethatshouldbeusedontheinput.Inaddition,we'realso
callingarenderButtonmethodthatwillrendertheSavebutton:
render(){
return(
<Viewstyle={styles.panel}>
<Textstyle={styles.instructions}>
Pleaseenteryourcontactinformation
</Text>
{this.renderTextfield({name:'name',placeholder:'Your
name'})}
{this.renderTextfield({name:'phone',placeholder:'Your
phonenumber',keyboard:'phone-pad'})}
{this.renderTextfield({name:'email',placeholder:'Your
emailaddress',keyboard:'email-address'})}
{this.renderButton()}
</View>
);
}
7. Torenderthetextfields,wearegoingtousetheTextInputcomponentinour
renderTextfieldmethod.ThisTextInputcomponentisprovidedbyReact
NativeandworksonbothiOSandAndroid.ThekeyboardTypeproperty
allowsustosetthekeyboardthatwewanttouse.Thefouravailable
keyboardsonbothplatformsaredefault,numeric,email-address,andphone-pad:
renderTextfield(options){
return(
<TextInput
style={styles.textfield}
onChangeText={(value)=>this.setState({[options.name]:
value})}
placeholder={options.label}
value={this.state[options.name]}
keyboardType={options.keyboard||'default'}
/>
);
}
8. WealreadyknowhowtorenderbuttonsandrespondtothePressaction.If
thisisunclear,IrecommendreadingtheCreatingareusablebuttonwith
themesupportrecipeinChapter3,ImplementingComplexUserInterfaces–
PartI:
renderButton(){
return(
<TouchableOpacity
onPress={this.handleButtonPress}
style={styles.button}
>
<Textstyle={styles.buttonText}>Save</Text>
</TouchableOpacity>
);
}
9. WeneedtodefinetheonPressButtoncallback.Forsimplicity,we'lljustshow
analertwiththeinputdatathatwehaveonthestateobject:
handleButtonPress=()=>{
const{name,phone,email}=this.state;
Alert.alert(`User'sdata`,`Name:${name},Phone:${phone},Email:
${email}`);
}
10. Wearealmostdonewiththisrecipe!Allweneedtodoisapplysomestyles
–somecolors,padding,andmargins;nothingfancyreally:
conststyles=StyleSheet.create({
panel:{
backgroundColor:'#fff',
borderRadius:3,
padding:10,
marginBottom:20,
},
instructions:{
color:'#bbb',
fontSize:16,
marginTop:15,
marginBottom:10,
},
textfield:{
height:40,
marginBottom:10,
},
button:{
backgroundColor:'#34495e',
borderRadius:3,
padding:12,
flex:1,
},
buttonText:{
textAlign:'center',
color:'#fff',
fontSize:16,
},
});
11. Ifwerunourapp,weshouldbeabletoseeaformthatusesnativecontrols
onbothAndroidandiOS,asexpected:
YoumightnotbeabletoseethekeyboardasdefinedbykeyboardTypewhenrunningyourappin
asimulator.RuntheapponarealdevicetoensurethatthekeyboardTypeisproperlychanging
thekeyboardforeachTextInput.
Howitworks...
Instep8,wedefinedtheTextInputcomponent.InReact(andReactNative),we
canusetwotypesofinput:controlledanduncontrolledcomponents.Inthis
recipe,we'reusingcontrolledinputcomponents,asrecommendedbytheReact
team.
Acontrolledcomponentwillhaveavalueproperty,andthecomponentwill
alwaysdisplaythecontentofthevalueproperty.Thismeansthatweneedaway
tochangethevaluewhentheuserstartstypingtheinput.Ifwedon'tupdatethat
value,thenthetextintheinputwon'teverchange,eveniftheusertriestotype
something.
Inordertoupdatethevalue,wecanusetheonChangeTextcallbackandsetthenew
value.Inthisexample,weareusingthestatetokeeptrackofthedataandweare
settinganewkeyonthestatewiththecontentoftheinput.
Anuncontrolledcomponent,ontheotherhand,willnothaveavalueproperty
assigned.WecanassignaninitialvalueusingthedefaultValueproperty.
Uncontrolledcomponentshavetheirownstate,andwecangettheirvalueby
usinganonChangeTextcallback,justaswecanwithcontrolledcomponents.
ImplementingComplexUser
Interfaces-PartIII
Inthischapter,wewillcoverthefollowingrecipes:
CreatingamapappwithGoogleMaps
Creatinganaudioplayer
Creatinganimagecarousel
Addingpushnotificationstoyourapp
Implementingbrowser-basedauthentication
Introduction
Inthischapter,we'llcoversomeofthemoreadvancedfeaturesyoumightneed
toaddtoanapp.Theapplicationswe'llbuildinthischapterincludebuildinga
fullyfunctionalaudioplayer,GoogleMapsintegration,andimplementing
browser-basedauthenticationsothatyourappcanconnecttopublicAPIsfor
developers.
CreatingamapappwithGoogle
Maps
Usingamobiledeviceisaportableexperience,soit'snosurprisethatmapsarea
commonpartofmanyiOSandAndroidapplications.Yourappmayneedtotell
auserwheretheyare,wherethey'regoing,orwhereotherusersareinrealtime.
Inthisrecipe,we'llbemakingasimpleappthatusesGoogleMapsonAndroid,
andApple'sMapsapponiOS,todisplayamapcenteredontheuser'slocation.
WewillbeusingExpo'sLocationhelperlibrarytogetthelatitudeandlongitudeof
theuserandwillusethatdatatorenderthemapusingExpo'sMapViewcomponent.
MapViewisanExporeadyversionofthereact-native-mapspackagecreatedby
Airbnb,soyoucanexpectthereact-native-mapsdocumentationtoapply,which
canbefoundathttps://github.com/react-community/react-native-maps.
Gettingready
Wewillneedtocreateanewappforthisrecipe.Let'scallitgoogle-maps.Sincethe
userpininthisrecipewilluseacustomicon,we'llalsoneedanimageforthat.I
usedtheiconYouAreHerebyMaicoAmorim,whichyoucandownloadfromht
tps://thenounproject.com/term/you-are-here/12314/.Feelfreetouseanyimageyou'd
liketorepresenttheuserpin.Savetheimagetotheassetsfolderintherootofthe
project.
Howtodoit...
1. We'llstartbyopeningApp.jsandaddingourimports:
importReactfrom'react';
import{
Location,
Permissions,
MapView,
Marker
}from'expo';
import{
StyleSheet,
Text,
View,
}from'react-native';
2. Next,let'sdefinetheAppclassandtheinitialstate.Inthisrecipe,statewill
onlyneedtokeeptrackoftheuser'slocation,whichweinitializetonull:
exportdefaultclassAppextendsComponent{
state={
location:null
}
//Definedinfollowingsteps
}
3. Next,we'lldefinethecomponentDidMountlifecyclehook,whichwillaskthe
usertograntpermissiontoaccesstheuser'slocationviathedevice's
geolocation.Iftheusergrantstheapppermissiontouseitslocation,the
returnobjectwillhaveastatuspropertywiththevalue'granted'.Ifgranted,
we'llgettheuser'slocationwiththis.getLocation,definedinthenextstep:
asynccomponentDidMount(){
constpermission=awaitPermissions.askAsync(Permissions.LOCATION);
if(permission.status==='granted'){
this.getLocation();
}
}
4. ThegetLocationfunctionissimple.Itgrabsthelocationinformationfromthe
device'sGPSusingthegetCurrentPositionAsyncmethodoftheLocation
component,thensavesthatlocationinformationtostate.Thatinformation
containsthelatitudeandlongitudeoftheuser,whichwe'llusewhenwe
renderthemap:
asyncgetLocation(){
letlocation=awaitLocation.getCurrentPositionAsync({});
this.setState({
location
});
}
5. Now,let'susethatlocationinformationtorenderourmap.First,we'llcheck
thatalocationhasbeensavedonstate.Ifso,we'llrendertheMapView,and
otherwiserendernull.Theonlypropertyweneedtosettorenderourmapis
theinitialRegionproperty,whichdefinesthelocationthemapshoulddisplay
whenitisfirstrendered.We'llpassthispropertyontheobjectwiththe
latitudeandlongitudesavedtostate,anddefineastartingzoomlevel
withlatitudeDeltaandlongitudeDelta:
renderMap(){
returnthis.state.location?
<MapView
style={styles.map}
initialRegion={{
latitude:this.state.location.coords.latitude,
longitude:this.state.location.coords.longitude,
latitudeDelta:0.09,
longitudeDelta:0.04,
}}
>
//Mapmarkerisdefinedinnextstep
</MapView>:null
}
6. WithintheMapView,we'llneedtoaddamarkerattheuser'scurrentlocation.
TheMarkercomponentispartoftheMapViewparentcomponent,sointheJSX
we'lldefineaMapView.MarkerchildelementoftheMapViewelement.This
elementtakestheuser'slocation,atitle,anddescriptionfordisplaying
whentheiconistapped,andacustomimageviatheimageproperty:
<MapView
style={styles.map}
initialRegion={{
latitude:this.state.location.coords.latitude,
longitude:this.state.location.coords.longitude,
latitudeDelta:0.09,
longitudeDelta:0.04,
}}
>
<MapView.Marker
coordinate={this.state.location.coords}
title={"UserLocation"}
description={"Youarehere!"}
image={require('./assets/you-are-here.png')}
/>
</MapView>:null
7. Now,let'sdefineourrenderfunction.Itsimplyrendersthemapwithina
containingViewelement:
render(){
return(
<Viewstyle={styles.container}>
{this.renderMap()}
</View>
);
}
8. Lastly,let'saddourstyles.We'llsetflexto1onboththecontainerandthe
map,sothatbothfillthescreen:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
},
map:{
flex:1
}
});
9. Now,ifweopentheapp,we'llseeamaprenderedwithourcustomuser
iconatthelocationprovidedbythedevice!Unfortunately,GoogleMaps
integrationwillnotworkintheAndroidemulator,soarealdevicewillbe
neededtotesttheAndroidimplementationoftheapp.Don'tbesurprised
thattheiOSapprunningonasimulatordisplaystheuser'slocationinSan
Francisco;thisisduetohowXcodelocationdefaultswork.Runitonareal
iOSdevicetoseetherenderyourlocation:
Howitworks...
BymakinguseoftheMapViewcomponentprovidedbyExpo,theimplementation
ofamapinyourReactNativeappisnowamuchsimplerandstraightforward
processthanitoncewas.
Instep3,wemadeuseofthePermissionshelperlibrary.Permissionshasamethod
calledaskAsync,whichtakesoneparameterdefiningwhattypeofpermissions
yourappwouldliketorequestfromtheuser.Permissionsalsohasconstantsfor
eachtypeofpermissionyoucanrequestfromtheuser.Thesepermissiontypes
includeLOCATION,NOTIFICATIONS(whichwe'lluselaterinthischapter),CAMERA,
AUDIO_RECORDING,CONTACTS,CAMERA_ROLL,andCALENDAR.Sinceweneedthelocationinthis
recipe,wepassedintheconstantPermissions.LOCATION.OncetheaskAsyncreturn
promiseresolves,thereturnobjectwillhaveastatuspropertyandanexpiration
property.Iftheuserhasallowedtherequestedpermission,statuswillbesetto
the'granted'string.Ifgranted,wewillfireoffourgetLocationmethod.
Instep4,wedefinedthefunctionthatgetsthelocationfromthedevice'sGPS.
WecallthegetCurrentPositionAsyncmethodoftheLocationcomponent.Thismethod
willreturnanobjectwithacoordspropertyandatimestampproperty.Thecoords
propertygivesusaccesstothelatitudeandlongitude,aswellasthealtitude,
accuracy(radiusofuncertaintyforthelocation,measuredinmeters),
altitudeAccuracy(accuracyofthealtitudevalue,inmeters(iOSonly)),heading,and
speed.Oncereceived,wesavethelocationtostatesothattherenderfunctionwill
becalled,andourmapwillberendered.
Instep5,wedefinedtherenderMapmethodtorenderthemap.First,wecheck
whetherthereisalocation,andifthereis,werendertheMapViewelement.This
elementonlyrequiresustodefinethevalueforoneproperty:initialRegion.This
propertytakesanobjectwithfourproperties:latitude,longitude,latitudeDelta,and
longitudeDelta.Wesetthelatitudeandlongitudeequaltothoseinthestateobject,
andprovideinitialvaluesforlatitudeDeltaandlongitudeDelta.Theselasttwo
propertiesdictatetheinitialzoomlevelthatthemapshouldberenderedat;the
largerthisnumberis,themorezoomedoutthemapwillbe.Isuggest
experimentingwiththesetwovaluestoseehowtheyaffecttherenderedmap.
Instep6,weaddedthemarkertothemapbyaddingaMapView.Markerelementasa
childoftheMapViewelement.Wedefinedthecoordinatesbypassingtheinfosaved
onstate(state.location.coords)tothecoordsproperty,andsetatitleanddescription
forthemarker'spopupwhentapped.Wewerealsoabletoeasilydefineacustom
pinbyinliningourcustomimagewitharequirestatementintheimageproperty.
There'smore...
Asmentionedpreviously,youcanreadthedocsforthereact-native-mapsproject
tolearnmoreaboutthefeaturesofthisexcellentlibrary(https://github.com/react-c
ommunity/react-native-maps).Forinstance,youcaneasilycustomizetheappearance
ofyourmapbyusingGoogleMapsStylingWizard(https://mapstyle.withgoogle.com
/)togenerateamapStyleJSONobject,thenpassthatobjecttotheMapView
component'scustomMapStyleproperty.Or,youcouldaddgeometricshapestoyour
mapwiththePolygonandCirclecomponents.
Onceyou'rereadytodeployyourapp,thereareafewfollow-upstepsthatyou
willneedtotaketotaketoensurethemapworksproperlyonAndroid.Youcan
readthedetailsonhowdeployingtoastandaloneAndroidappwith
aMapViewcomponentworksintheExpodocumentationathttps://docs.expo.io/versi
ons/latest/sdk/map-view#deploying-to-a-standalone-app-on-android.
Creatinganaudioplayer
Audioplayersareanothercommoninterfacebuiltintomanyapplications.
Whetheryourappneedstoplayaudiofilesstoredlocallyonthedeviceorstream
audiofromaremotelocation,Expo'sAudiocomponentcomestotherescue.
Inthisrecipe,we'llbebuildingafull-fledgedbasicaudioplayer,with
play/pause,nexttrack,andprevioustrackfunctionality.Forsimplicity,we'llbe
hardcodingtheinformationforthetrackswe'llbeusing,butinareal-world
scenario,you'lllikelybeworkingwithsimilarobjectstowhatwe'redefining:an
objectwithatracktitle,albumname,artistname,andaURLtoaremoteaudio
file.I'vechosenthreerandomlivetracksfromtheInternetArchive'sLiveMusic
Archive(https://archive.org/details/etree).
Gettingready
We'llneedtocreateanewappforthisrecipe.Let'scallitaudio-player.
Howtodoit...
1. Let'sstartbyopeningupApp.jsandaddingthedependencieswe'llneed:
importReact,{Component}from'react';
import{Audio}from'expo';
import{Feather}from'@expo/vector-icons';
import{
StyleSheet,
Text,
TouchableOpacity,
View,
Dimensions
}from'react-native';
2. Anaudioplayerneedsaudiotoplay.We'llcreateaplaylistarraytoholdthe
audiotracks.Eachtrackisrepresentedbyanobjectwithatitle,artist,album,
anduri:
constplaylist=[
{
title:'PeopleWatching',
artist:'KellerWilliams',
album:'KellerWilliamsLiveatTheWestcottTheateron2012-09-22',
uri:'https://ia800308.us.archive.org/7/items/kwilliams2012-09-22.at853.flac16/kwilliams2012-09-22at853.t16.mp3'
},
{
title:'HuntedByAFreak',
artist:'Mogwai',
album:'MogwaiLiveatAncienneBelgiqueon2017-10-20',
uri:'https://ia601509.us.archive.org/17/items/mogwai2017-10-20.brussels.fm/Mogwai2017-10-20Brussels-07.mp3'
},
{
title:'NervousTicMotionoftheHeadtotheLeft',
artist:'AndrewBird',
album:'AndrewBirdLiveatRioTheateron2011-01-28',
uri:'https://ia800503.us.archive.org/8/items/andrewbird2011-01-28.early.dr7.flac16/andrewbird2011-01-28.early.t07.mp3'
}
];
3. Next,we'lldefineourAppclassandinitialstateobjectwithfourproperties:
isPlayingfordefiningwhethertheplayerisplayingorpaused
playbackInstancetoholdtheAudioinstance
volumeandcurrentTrackIndexforthecurrentlyplayingtrack
isBufferingtodisplayaBuffering...messagewhilethetrackis
bufferingatthebeginningofplayback
Asshowninfollowingcode:
exportdefaultclassAppextendsComponent{
state={
isPlaying:false,
playbackInstance:null,
volume:1.0,
currentTrackIndex:0,
isBuffering:false,
}
//Definedinfollowingsteps
}
4. Let'sdefinethecomponentDidMountlifecyclehooknext.We'llusethismethod
toconfiguretheAudiocomponentviathesetAudioModeAsyncmethod,passingin
anoptionsobjectwithafewrecommendedsettings.Thesewillbediscussed
moreintheHowitworks...sectionattheendoftherecipe.Afterthis,we'll
loadtheaudiowithloadAudio,definedinthenextstep:
asynccomponentDidMount(){
awaitAudio.setAudioModeAsync({
allowsRecordingIOS:false,
interruptionModeIOS:Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS:true,
shouldDuckAndroid:true,
interruptionModeAndroid:Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
});
this.loadAudio();
}
5. TheloadAudiofunctionwillhandleloadingtheaudioforourplayer.First,
we'llcreateanewinstanceofAudio.Sound.We'llthencallthe
setOnPlaybackStatusUpdatemethodonournewAudioinstance,passingina
handlerthatwillbecalledwheneverthestateofplaybackwithinthe
instancehaschanged.Finally,wecallloadAsyncontheinstance,passingita
sourcefromtheplaylistarray,aswellasastatusobjectwiththevolumeand
ashouldPlaypropertysettotheisPlayingvalueofstate.Thethirdparameter
dictateswhetherwewanttowaitforthefiletofinishdownloadingbeforeit
isplayed,sowepassinfalse:
asyncloadAudio(){
constplaybackInstance=newAudio.Sound();
constsource={
uri:playlist[this.state.currentTrackIndex].uri
}
conststatus={
shouldPlay:this.state.isPlaying,
volume:this.state.volume,
};
playbackInstance.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
awaitplaybackInstance.loadAsync(source,status,false);
this.setState({
playbackInstance
});
}
6. Westillneedtodefinethecallbackforhandlingstatusupdates.Allweneed
todointhisfunctionissetthevalueofisBufferingonstatetotheisBuffering
valueonthestatusparameterthatwaspassedinfromthe
setOnPlaybackStatusUpdatefunctioncall:
onPlaybackStatusUpdate=(status)=>{
this.setState({
isBuffering:status.isBuffering
});
}
7. Ourappnowknowshowtoloadanaudiofilefromtheplaylistarrayand
updatestatewiththecurrentbufferingstatusoftheloadedaudiofile,which
we'lluselaterintherenderfunctiontodisplayamessagetotheuser.All
that'sleftistoaddthebehaviorfortheplayeritself.First,we'llhandlethe
play/pausestate.ThehandlePlayPausemethodchecksthevalue
ofthis.state.isPlayingtodeterminewhetherthetrackshouldbeplayedor
paused,andcallstheassociatedmethodontheplaybackInstanceaccordingly.
Finally,weneedtoupdatethevalueofisPlayingforstate:
handlePlayPause=async()=>{
const{isPlaying,playbackInstance}=this.state;
isPlaying?awaitplaybackInstance.pauseAsync():awaitplaybackInstance.playAsync();
this.setState({
isPlaying:!isPlaying
});
}
8. Next,let'sdefinethefunctionforhandlingskippingtotheprevioustrack.
First,we'llclearthecurrenttrackfromtheplaybackInstanceby
callingunloadAsync.We'llupdatethecurrentTrackIndexvalueofstatetoeither
onelessthanthecurrentvalue,or0ifwe'reatthebeginningoftheplaylist
array.Then,we'llcallthis.loadAudiotoloadthepropertrack:
handlePreviousTrack=async()=>{
let{playbackInstance,currentTrackIndex}=this.state;
if(playbackInstance){
awaitplaybackInstance.unloadAsync();
currentTrackIndex<playlist.length-1?currentTrackIndex
+=1:currentTrackIndex=0;
this.setState({
currentTrackIndex
});
this.loadAudio();
}
}
9. Notsurprisingly,handleNextTrackisthesameastheprecedingfunction,but
thistimewe'lleitheradd1tothecurrentindex,orsettheindexto0ifwe're
attheendoftheplaylistarray:
handleNextTrack=async()=>{
let{playbackInstance,currentTrackIndex}=this.state;
if(playbackInstance){
awaitplaybackInstance.unloadAsync();
currentTrackIndex<playlist.length-1?currentTrackIndex+=
1:currentTrackIndex=0;
this.setState({
currentTrackIndex
});
this.loadAudio();
}
}
10. It'stimetodefineourrenderfunction.Wewillneedthreebasicpiecesinour
UI:a'Buffering...'messagewhenthetrackisplayingbutstillbuffering,a
sectionfordisplayinginformationforthecurrenttrack,andasectionto
holdtheplayer'scontrols.The'Buffering...'messagewillonlydisplayif
boththis.state.isBufferingandthis.state.isPlayingaretrue.Thesonginfois
renderedviatherenderSongInfomethod,whichwe'lldefineinstep12:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={[styles.largeText,styles.buffer]}>
{this.state.isBuffering&&this.state.isPlaying?
'Buffering...':null}
</Text>
{this.renderSongInfo()}
<Viewstyle={styles.controls}>
//Definedinnextstep.
</View>
</View>
);
}
11. TheplayercontrolsaremadeupofthreeTouchableOpacitybuttonelements,
eachwithacorrespondingiconfromtheFeathericonlibrary.Youcanfind
moreinformationonusingiconsinChapter3,ImplementingComplexUser
Interfaces–PartI.We'lldeterminewhethertodisplaythePlayiconorthe
Pauseicondependingonthevalueofthis.state.isPlaying:
<Viewstyle={styles.controls}>
<TouchableOpacity
style={styles.control}
onPress={this.handlePreviousTrack}
>
<Feathername="skip-back"size={32}color="#fff"/>
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handlePlayPause}
>
{this.state.isPlaying?
<Feathername="pause"size={32}color="#fff"/>:
<Feathername="play"size={32}color="#fff"/>
}
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handleNextTrack}
>
<Feathername="skip-forward"size={32}color="#fff"/>
</TouchableOpacity>
</View>
12. TherenderSongInfomethodreturnsbasicJSXfordisplayingthemetadata
associatedwiththetrackcurrentlyplaying:
renderSongInfo(){
const{playbackInstance,currentTrackIndex}=this.state;
returnplaybackInstance?
<Viewstyle={styles.trackInfo}>
<Textstyle={[styles.trackInfoText,styles.largeText]}>
{playlist[currentTrackIndex].title}
</Text>
<Textstyle={[styles.trackInfoText,styles.smallText]}>
{playlist[currentTrackIndex].artist}
</Text>
<Textstyle={[styles.trackInfoText,styles.smallText]}>
{playlist[currentTrackIndex].album}
</Text>
</View>
:null;
}
13. Allthat'slefttoaddarethestyles.Thestylesdefinedherearewell-covered
groundbynow,anddon'tgobeyondcentering,colors,fontsize,andadding
paddingandmargins:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#191A1A',
alignItems:'center',
justifyContent:'center',
},
trackInfo:{
padding:40,
backgroundColor:'#191A1A',
},
buffer:{
color:'#fff'
},
trackInfoText:{
textAlign:'center',
flexWrap:'wrap',
color:'#fff'
},
largeText:{
fontSize:22
},
smallText:{
fontSize:16
},
control:{
margin:20
},
controls:{
flexDirection:'row'
}
});
14. Youcannowcheckoutyourappinthesimulator,andyoushouldhavea
fullyworkingaudioplayer!NotethataudioplaybackintheAndroid
emulatormaybetooslowfortheplaybacktoworkproperly,andmay
soundverychoppy.OpentheapponarealAndroiddevicetohearthetrack
playingproperly:
Howitworks...
Instep4,weinitializedoptionsontheAudiocomponentoncetheapphadfinished
loadingviathecomponentDidMountmethod.TheAudiocomponent'ssetAudioModeAsync
methodtakesanoptionobjectasitsonlyparameter.
Let'sreviewtheoptionsweusedinthisrecipe:
interruptionModeIOSandinterruptionModeAndroidsethowtheaudioinyourapp
shouldinteractwiththeaudiofromotherapplicationsonthedevice.We
usedtheAudiocomponent'sINTERRUPTION_MODE_IOS_DO_NOT_MIX
andINTERRUPTION_MODE_ANDROID_DO_NOT_MIXenums,respectively,todeclarethatour
app'saudioshouldinterruptanyotherapplicationsplayingaudio.
playsInSilentModeIOSisaBooleanthatdetermineswhetheryourappshould
playaudiowhenthedeviceisinsilentmode.
shouldDuckAndroidisaBooleanthatdetermineswhetheryourapp'saudio
shouldlowerinvolume(duck)whenaudiofromanotherappinterrupts
yourapp.Whilethissettingdefaultstotrue,I'veaddedittotherecipeso
thatyou'reawarethatit'sanoption.
Instep5,wedefinedtheloadAudiomethod,whichperformstheheavyliftingin
thisrecipe.First,wecreatedanewinstanceoftheAudio.Soundclassandsaveditto
theplaybackInstancevariableforlateruse.Next,wesetthesourceandstatus
variablesthatwillbepassedintotheloadAsyncfunctionontheplaybackInstancefor
actuallyloadingtheaudiofile.Inthesourceobject,wesettheuripropertytothe
correspondinguripropertyontheobjectintheplaylistarrayattheindexstored
inthis.state.currentTrackIndex.Inthestatusobject,wesetthevolumetothevolume
valuesavedonstate,andsetshouldPlay,aBooleanthatdetermineswhetherthe
audioshouldbeplayinginitially,tothis.state.isPlaying.And,sincewewantto
streamtheremoteMP3fileinsteadofwaitingfortheentirefiletodownload,we
passfalsethethird,downloadFirst,parameter.
BeforecallingtheloadAsyncmethod,wefirst
calledsetOnPlaybackStatusUpdateofplaybackInstance,whichtakesacallbackfunction
thatshouldbecalledwhenthestateofplaybackInstancehaschanged.Wedefined
thathandlerinstep6.ThehandlersimplysavestheisBufferingvaluefromthe
callback'sstatusparametertotheisBufferingpropertyofstate,whichwillfirea
rerender,updatingthe'Buffering...'messageintheUIaccordingly.
Instep7,wedefinedthehandlePlayPausefunctionfortogglingplayandpause
functionalityintheapp.Ifthere'satrackplaying,this.state.isPlayingwillbetrue,
sowe'llcallthepauseAsyncfunctionontheplaybackInstanceotherwise,we'llcall
playAsynctostartplayingtheaudioagain.Oncewe'veplayedorpaused,we
updatethevalueofisPlayingonstate.
Instep8andstep9,wecreatedthefunctionsthathandleskippingtothenext
andprevioustracks.Eachofthesefunctionsincreasesordecreasesthevalueof
this.state.currentTrackIndexasappropriate,sothatbythetimethis.loadAudiois
calledatthebottomofeachfunction,itwillloadthetrackassociatedwiththe
objectintheplaylistarrayatthenewindex.
There'smore...
Thefeaturesofourcurrentapparemorebasicthanyou'llfindinmostaudio
players,butallthetoolsyouneedforbuildingafeature-richaudioplayerareat
yourdisposal.Forinstance,youcoulddisplaythecurrenttracktimeintheUIby
tappingintothepositionMillispropertyonthestatusparameterinthe
setOnPlaybackStatusUpdatecallback.Or,youcoulduseaReactNativeSlider
componenttoallowtheusertoadjustthevolumeorplaybackrate.Expo'sAudio
componentprovidesallthebuildingblocksforagreataudioplayerapp.
Creatinganimagecarousel
Thereareallkindsofapplicationsthatmakeuseofimagecarousels.Anytime
there'sacollectionofimagesthatyou'dlikeyourusertobeabletoperuse,a
carouselislikelyamongthemosteffectiveUIpatternsforaccomplishingthe
task.
ThereareanumberofpackagesintheReactNativecommunityforhandlingthe
creationofcarousels,butinmyexperiencenonearemorestableormore
versatilethanreact-native-snap-carousel(https://github.com/archriss/react-native-sn
ap-carousel).ThispackageprovidesagreatAPIforcustomizingthelookand
behaviorofyourcarousel,andsupportsExpoappdevelopmentwithouttheneed
fordetaching.Youcaneasilychangehowslidesappearastheyslideinandout
ofthecarouselframeviatheCarouselcomponent'slayoutproperty,andasof
version3.6,youcanevencreatecustominterpolations!
Whileyouarenotlimitedtoonlydisplayingimageswiththispackage,we'llbe
buildingacarouselthatjustdisplaysimagesalongwithacaptiontokeepthe
recipesimple.We'llbeusingtheexcellentlicense-freephotositeunsplash.comto
getrandomimagesfordisplayinginourcarouselviatheUnsplashSource
projecthostedatsource.unsplash.com.UnsplashSourceallowsyoutoeasilyrequest
randomimagesfromUnsplashwithoutneedingtoaccesstheofficialAPI.You
canvisittheUnsplashSourcesiteformoreinformationonhowitworks.
Gettingready
We'llneedtocreateanewappforthisrecipe.Let'scallthisappcarousel.
Howtodoit...
1. We'llstartbyopeningApp.jsandimportingdependencies:
importReact,{Component}from'react';
import{
SafeAreaView,
StyleSheet,
Text,
View,
Image,
TouchableOpacity,
Picker,
Dimensions,
}from'react-native';
importCarouselfrom'react-native-snap-carousel';
2. Next,let'sdefinetheAppclassandtheinitialstateobject.Thestatehasthree
properties:aBooleanforwhetherwe'recurrentlydisplayingthecarouselor
not,alayoutTypepropertyforsettingthelayoutstyleofourcarousel,andan
arrayofimageSearchTermswe'lluselatertogetimagesfromUnsplashSource.
FeelfreetochangetheimageSearchTermsarraytoyourheart'scontent:
exportdefaultclassAppextendsReact.Component{
state={
showCarousel:false,
layoutType:'default',
imageSearchTerms:[
'Books',
'Code',
'Nature',
'Cats',
]
}
//Definedinfollowingsteps
}
3. Let'sdefinetherendermethodnext.We'lljustcheckthevalueof
this.state.showCorouselandeithershowthecarouselorthecontrols
accordingly:
render(){
return(
<SafeAreaViewstyle={styles.container}>
{this.state.showCarousel?
this.renderCarousel():
this.renderControls()
}
</SafeAreaView>
);
}
4. Next,let'screatetherenderControlsfunction.Thiswillbethelayouttheuser
seeswhentheyfirstopentheapp,andconsistsofaReactNativePickerfor
selectingalayouttypetouseinthecarouselandabuttonforopeningthe
carousel.ThePickerhasthreeoptionsavailable:default,tinder,andstack:
renderControls=()=>{
return(
<Viewstyle={styles.container}>
<Picker
selectedValue={this.state.layoutType}
style={styles.picker}
onValueChange={this.updateLayoutType}
>
<Picker.Itemlabel="Default"value="default"/>
<Picker.Itemlabel="Tinder"value="tinder"/>
<Picker.Itemlabel="Stack"value="stack"/>
</Picker>
<TouchableOpacity
onPress={this.toggleCarousel}
style={styles.openButton}
>
<Textstyle={styles.openButtonText}>OpenCarousel</Text>
</TouchableOpacity>
</View>
)
}
5. Let'sdefinethetoggleCarouselfunction.Thisfunctionsimplysetsthevalue
ofshowCarouselonstatetoitsopposite.Bydefiningatogglefunction,wecan
usethesamefunctiontobothopenandclosethecarousel:
toggleCarousel=()=>{
this.setState({
showCarousel:!this.state.showCarousel
});
}
6. Similarly,theupdateLayoutTypemethodjustupdatesthelayoutTypeonstateto
thelayoutTypevaluepassedintoitfromthePickercomponent:
updateLayoutType=(layoutType)=>{
this.setState({
layoutType
});
}
7. TherenderCarouselfunctionreturnsthemarkupforthecarousel.It'smadeup
ofabuttonforclosingthecarouselandtheCarouselcomponentitself.This
componenttakesalayoutproperty,assetbythePicker.Italsohasa
dataproperty,whichtakesthedatathatshouldbeloopedoverforeach
carouselslide,andarenderItemcallbackthathandlestherenderingofeach
individualslide:
renderCarousel=()=>{
return(
<Viewstyle={styles.carouselContainer}>
<Viewstyle={styles.closeButtonContainer}>
<TouchableOpacity
onPress={this.toggleCarousel}
style={styles.button}
>
<Textstyle={styles.label}>x</Text>
</TouchableOpacity>
</View>
<Carousel
layout={this.state.layoutType}
data={this.state.imageSearchTerms}
renderItem={this.renderItem}
sliderWidth={350}
itemWidth={350}
>
</Carousel>
</View>
);
}
8. Westillneedthefunctionthathandlestherenderingofeachslide.This
functionreceivesoneobjectparametercontainingthenextiteminthearray
passedtothedataproperty.We'llreturnanImagecomponentthatusesthe
itemparametervaluetogetarandomitemfromUnsplashSourcethat's
350x350insize.We'llalsoaddaTextelementtodisplaythetypeofimage
beingdisplayed:
renderItem=({item})=>{
return(
<Viewstyle={styles.slide}>
<Image
style={styles.image}
source={{uri:`https://source.unsplash.com/350x350/?
${item}`}}
/>
<Textstyle={styles.label}>{item}</Text>
</View>
);
}
9. Thelastthingwe'llneedissomestylestolayoutourUI.Thecontainer
stylesapplytothemainwrappingSafeAreaViewelement,soweset
justifyContentto'space-evenly'sothatthePickerandTouchableOpacity
componentsfillupthescreen.Todisplaytheclosebuttoninthetop-right
cornerofthescreen,we'llapplyflexDirection:'rowandjustifyContent:'flex-
end'tothewrappingelement.Therestofthestylesarejustdimensions,
colors,padding,margins,andfontsize:
conststyles=StyleSheet.create({
container:{
flex:1,
flexDirection:'column',
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'space-evenly',
},
carouselContainer:{
flex:1,
alignItems:'center',
justifyContent:'center',
backgroundColor:'#474747'
},
closeButtonContainer:{
width:350,
flexDirection:'row',
justifyContent:'flex-end'
},
slide:{
flex:1,
justifyContent:'center',
alignItems:'center',
},
image:{
width:350,
height:350,
},
label:{
fontSize:30,
padding:40,
color:'#fff',
backgroundColor:'#474747'
},
openButton:{
padding:10,
backgroundColor:'#000'
},
openButtonText:{
fontSize:20,
padding:20,
color:'#fff',
},
closeButton:{
padding:10
},
picker:{
height:150,
width:100,
backgroundColor:'#fff'
}
});
10. We'vecompletedourcarouselapp.Itprobablywon'twinanydesign
awards,butit'saworkingcarouselappwithsmooth,native-feeling
behavior:
Howitworks...
Instep4,wedefinedtherenderControlsfunction,whichrenderstheUIwhenthe
appisfirstlaunched.Thisisthefirstrecipeinwhichwe'veusedthePicker
component.It'sapartofthecoreReactNativelibraryandprovidesthedrop-
downtypeselectorusedtoselectoptionsinmanyapplications.TheselectedValue
propertyisthevaluetiedtowhicheveritemiscurrentlyselectedinthepicker.
Bysettingittothis.state.layoutType,we'lldefaulttheselectiontothe'default'
layout,andkeepthevaluessyncedwhenadifferentPickeritemisselected.Each
iteminthepickerisrepresentedbyaPicker.Itemcomponent.Itslabelproperty
definesthedisplaytextfortheitem,andthevaluepropertyrepresentsthestring
valuefortheitem.SinceweprovidedtheonValueChangepropertywiththe
updateLayoutTypefunction,itwillbecalledwheneveranewitemisselected,which
inturnwillupdatethis.state.layoutTypeaccordingly.
Instep7,wedefinedtheJSXforthecarousel.Thecarousel'sdataandrenderItem
propertiesarerequired,andworktogethertorendereachslideinthecarousel.
Whenthecarouselisinstantiated,thearraypassedintothedatapropertywillbe
loopedover,andtherenderItemcallbackfunctionwillbecalledforeachitemin
thearea,withthatitempassedintotherenderItemasaparameter.Wealsosetthe
sliderWidthanditemWidthproperties,whicharerequiredforhorizontalcarousels.
Instep8,wedefinedtherenderItemfunctionthatgetscalledforeachentryinthe
arraypassedintodata.WesetthesourceofthereturnedImagecomponenttoan
UnsplashsourceURL,whichwillreturnarandomimageofthetyperequested.
There'smore...
Thereareafewthingswecoulddotoimprovethisrecipe.Wecouldmakeuseof
theImage.prefetch()methodtodownloadthefirstimagebeforeopeningthe
carousel,sothattheimageisreadyrightaway,oraddaninputtoallowtheuser
toselecttheirownimagesearchterms.
Thereact-native-snap-carouselpackageprovidesagreatwaytobuilda
multimediacarouselforaReactNativeapp.Thereareanumberoffeatureswe
didn'thavethetimetocoverhere,includingparallaximagesandcustom
pagination.Fortheadventurousdeveloper,thepackageprovidesawaytocreate
custominterpolations,allowingyoutomakeyourownlayoutsbeyondthethree
built-inlayouts.
Addingpushnotificationstoyourapp
Pushnotificationsareagreatwaytoprovideaconstantfeedbackloopbetween
theappandtheuserbycontinuallyprovidingapp-specificdatathat'srelevantto
theuser.Messagingapplicationssendnotificationswhennewmessagesarrive.
Reminderapplicationsdisplayanotificationtoremindtheuserofataskata
specifictimeorlocation.Apodcastappmightusenotificationstoinformthe
userthatanewepisodehasbeenpublished.Ashoppingappcoulduse
notificationstoalerttheusertocheckoutalimited-timedeal.
Pushnotificationsareaprovenwaytoincreaseuserinteractionandretention.If
yourappmakesuseoftime-sensitiveorevent-baseddata,pushnotifications
couldbeavaluableasset.Inthisrecipe,we'llbeusingExpo'spushnotification
implementation,whichsimplifiessomeofthesetupthatwouldberequiredwith
avanillaReactNativeproject.Iftheneedsofyourappdemandanon-Expo
project,Iwouldrecommendconsideringthereact-native-push-notification
packageathttps://github.com/zo0r/react-native-push-notification.
Inthisrecipe,we'llbemakingaverysimplisticmessagingappwithpush
notifications.We'llrequestproperpermissions,thenregisterapushnotification
tokentoanExpressserverwe'llbebuilding.We'llalsorenderaTextInputforthe
usertoenteramessageinto.WhentheSendbuttonispressed,themessagewill
besenttoourserver,andtheserverwillsendapushnotificationviaExpo'spush
notificationserver,withthemessagefromtheapp,toalldevicesthathave
registeredatokenwithourExpressserver.
ThankstoExpo'sbuilt-inpushnotificationservice,thecomplicatedworkof
creatinganotificationforeachnativedeviceisoffloadedtoanExpohosted
backend.TheExpressserverwebuildinthisrecipewilljustpassoffJSON
objectsforeachpushnotificationtotheExpobackend,andtherestistakencare
of.ThefollowingdiagramfromtheExpodocs(https://docs.expo.io/versions/latest
/guides/push-notifications)illustratesthelifecycleofapushnotification:
Imagesource:https://docs.expo.io/versions/latest/guides/push-notifications/
WhileimplementingpushnotificationsusingExpoislesssetupworkthanit
wouldotherwisebe,therequirementsofthetechnologystillmeanwewillneed
torunaserverforhandlingregistrationsandsendingnotifications,whichmeans
thisrecipewillbealittlelongerthanmost.Let'sgetstarted!
Gettingready
Oneofthefirstthingswe'llneedtodointhisappisrequestpermissionfromthe
devicetousepushnotifications.Unfortunately,pushnotificationpermissionsdo
notworkproperlyinemulators,soarealdevicewillbeneededtotestthisapp.
We'llalsoneedtobeabletoaccessthepushnotificationserverfromanaddress
outsideofthelocalhost.Inareal-worldsetup,thepushnotificationserverwould
alreadyhaveapublicURL,butinadevelopmentenvironment,theeasiest
solutionistocreateatunnelthatexposesthedevelopmentpushnotification
servertotheinternet.We'llbeusingthengroktoolforthispurpose,sinceitisa
mature,robust,andincrediblyeasy-to-usesolution.Youcanreadmoreaboutthe
softwareathttps://ngrok.com.
First,installngrokgloballyvianpmusingthefollowingcommand:
npmi-gngrok
Onceit'sinstalled,youcancreateatunnelfromtheinternettoaportonyour
localmachinebyexecutingngrokwiththehttpsparameter:
ngrokhttps[port-to-expose]
We'llusethiscommandlaterintherecipetoexposethedevelopmentserver.
Let'screateanewappforthisrecipe.We'llcallitpush-notifications.We'regoing
toneedthreeextranpmpackagesforthisrecipe:expressforthepushnotification
server,esmforusingES6syntaxsupportontheserver,andexpo-server-sdkfor
processingpushnotifications.Installthemwithyarn:
yarnaddexpressesmexpo-server-sdk
Alternatively,installthemusingnpm:
npminstallexpressesmexpo-server-sdk--save
Howtodoit...
1. Let'sstartwithbuildingtheApp.We'llstartthatbyaddingthedependencies
weneedtoApp.js:
importReactfrom'react';
import{
StyleSheet,
Text,
View,
TextInput,
Modal,
TouchableOpacity
}from'react-native';
import{Permissions,Notifications}from'expo';
2. We'regoingtodeclaretwoconstantsfortheAPIendpointsonourserver,
buttheurlwillbegeneratedbyngrokwhenweruntheserverlaterinthe
recipe,sowe'llupdatethevalueoftheseconstantsatthatpoint:
constPUSH_REGISTRATION_ENDPOINT='http://generated-ngrok-url/token';
constMESSAGE_ENPOINT='http://generated-ngrok-url/message';
3. Let'screatetheAppcomponentandinitializethestateobject.We'llneed
anotificationpropertytoholdnotificationsreceivedbytheNotifications
listener,whichwewilldefineinalaterstep:
exportdefaultclassAppextendsReact.Component{
state={
notification:null,
messageText:''
}
//Definedinfollowingsteps
}
4. Let'sdefinethemethodthatwillhandleregisteringthepushnotification
tokentotheserver.We'llaskfornotificationpermissionfromtheuservia
theaskAsyncmethodonthePermissionscomponent.Ifpermissionisgranted,
getthetokenfromthedevicefromthegetExpoPushTokenAsyncmethodof
theNotificationscomponent:
registerForPushNotificationsAsync=async()=>{
const{status}=awaitPermissions.askAsync(Permissions.NOTIFICATIONS);
if(status!=='granted'){
return;
}
lettoken=awaitNotifications.getExpoPushTokenAsync();
//Definedinfollowingsteps
}
5. Oncewehavetheappropriatetoken,we'llsenditovertothepush
notificationserverforregistration.WewillthenmakeaPOSTrequest
toPUSH_REGISTRATION_ENDPOINT,sendingatokenobjectanduserobjectinthe
requestbody.I'vehardcodedthevaluesintheuserobject,butinarealapp
thiswouldbethemetadatayou'vestoredforthecurrentuser:
registerForPushNotificationsAsync=async()=>{
//Definedinabovestep
returnfetch(PUSH_REGISTRATION_ENDPOINT,{
method:'POST',
headers:{
'Accept':'application/json',
'Content-Type':'application/json',
},
body:JSON.stringify({
token:{
value:token,
},
user:{
username:'warly',
name:'DanWard'
},
}),
});
//Definedinnextstep
}
6. Afterthetokenisregistered,we'llsetupaneventlistenertolistentoany
notificationsthatoccurwhiletheappisopenandforegrounded.Incertain
cases,wewillneedtomanuallyhandledisplayingtheinformationfroman
incomingpushnotification.ChecktheHowitworks...sectionattheendof
thisrecipeformoreonwhythisisnecessaryandhowitcanbeleveraged.
We'lldefinethehandlerinthenextstep:
registerForPushNotificationsAsync=async()=>{
//Definedinabovesteps
this.notificationSubscription=
Notifications.addListener(this.handleNotification);
}
7. Wheneveranewnotificationisreceived,thehandleNotificationmethodwill
berun.We'lljuststorethenewnotificationpassedtothiscallbackonthe
stateobjectforlateruseintherenderfunction:
handleNotification=(notification)=>{
this.setState({notification});
}
8. Wewantourapptoaskforpermissiontousepushnotifications,andto
registerthepushnotificationtokenwhentheapplaunches.We'llutilizethe
componentDidMountlifecyclehooktorunourregisterForPushNotificationsAsync
method:
componentDidMount(){
this.registerForPushNotificationsAsync();
}
9. TheUIwillbeveryminimaltokeeptherecipesimple.It'smadeupof
aTextInputforthemessagetext,aSendbuttonforsendingthemessage,and
aViewfordisplayinganynotificationsheardbythenotificationlistener:
render(){
return(
<Viewstyle={styles.container}>
<TextInput
value={this.state.messageText}
onChangeText={this.handleChangeText}
style={styles.textInput}
/>
<TouchableOpacity
style={styles.button}
onPress={this.sendMessage}
>
<Textstyle={styles.buttonText}>Send</Text>
</TouchableOpacity>
{this.state.notification?
this.renderNotification()
:null}
</View>
);
}
10. TheTextInputcomponentdefinedinthepreviousstepismissingthemethod
itneedsforitsonChangeTextproperty.Let'screatethatmethodnext.Itjust
savesthetextinputbytheusertothis.state.messageTextsoitcanbeusedby
thevaluepropertyandelsewhere:
handleChangeText=(text)=>{
this.setState({messageText:text});
}
11. TheTouchableOpacitycomponent'sonPresspropertycallsthesendMessagemethod
tosendthemessagetextwhentheuserpressesthebutton.Inthisfunction,
we'lljusttakethemessagetextandPOSTittotheMESSAGE_ENDPOINTonourpush
notificationserver.Theserverwillhandlethingsfromthere.Oncethe
messageissent,we'llclearthemessageTextpropertyonstate:
sendMessage=async()=>{
fetch(MESSAGE_ENPOINT,{
method:'POST',
headers:{
Accept:'application/json',
'Content-Type':'application/json',
},
body:JSON.stringify({
message:this.state.messageText,
}),
});
this.setState({messageText:''});
}
12. ThelastpieceweneedfortheAppisthestyles.Thesestylesare
straightforward,andshouldalllookquitefamiliarbynow:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#474747',
alignItems:'center',
justifyContent:'center',
},
textInput:{
height:50,
width:300,
borderColor:'#f6f6f6',
borderWidth:1,
backgroundColor:'#fff',
padding:10
},
button:{
padding:10
},
buttonText:{
fontSize:18,
color:'#fff'
},
label:{
fontSize:18
}
});
13. WiththeReactNativeappportionoutoftheway,let'smoveontothe
serverportion.First,we'llcreateanewserverfolderintherootofthe
projectwithanindex.jsfileinsideofit.Let'sstartbyimportingexpresstorun
theserverandexpo-server-sdktohandletheregistrationandsendingofpush
notifications.We'llcreateanExpressserverappandstoreitintheappconst,
andanewinstanceoftheExposerverSDKintheexpoconst.We'llalso
addasavedPushTokensarrayforstoringanytokensthatareregisteredwiththe
ReactNativeapp,andaPORT_NUMBERconstfortheportwewanttorunthe
serveron:
importexpressfrom'express';
importExpofrom'expo-server-sdk';
constapp=express();
constexpo=newExpo();
letsavedPushTokens=[];
constPORT_NUMBER=3000;
14. Ourserverwillneedtoexposetwoendpoints(oneforregisteringtokens,
andoneforacceptingmessagesfromtheReactNativeapp),sowe'llcreate
twofunctionsthatwillbeexecutedwhentheseroutesarehit.We'lldefine
thesaveTokenfunctionfirst.Itjusttakesatoken,checkswhetherit'sstoredin
thesavedPushTokensarray,andpushesittothearrayifitisn'ttherealready:
constsaveToken=(token)=>{
if(savedPushTokens.indexOf(token===-1)){
savedPushTokens.push(token);
}
}
15. Theotherfunctionourserverneedsisahandlerforsendingpush
notificationswhenamessageisreceivedfromtheReactNativeapp.We'll
loopoverallofthetokensthathavebeensavedtothesavedPushTokensarray
andcreateamessageobjectforeachtoken.Eachmessageobjecthasatitle
ofMessagereceived!,whichwilldisplayinboldonthepushnotification,and
themessagetextasthebodyofthenotification:
consthandlePushTokens=(message)=>{
letnotifications=[];
for(letpushTokenofsavedPushTokens){
if(!Expo.isExpoPushToken(pushToken)){
console.error(`Pushtoken${pushToken}isnotavalidExpopushtoken`);
continue;
}
notifications.push({
to:pushToken,
sound:'default',
title:'Messagereceived!',
body:message,
data:{message}
})
}
//Definedinfollowingstep
}
16. Oncewehaveanarrayofmessages,wecansendthemtoExpo'sserver,
whichinturnwillsendthepushnotificationtoallregistereddevices.We'll
sendthemessagesarrayviatheexpo
server'schunkPushNotificationsandsendPushNotificationsAsyncmethods,and
console.logthesuccessreceipts,oranerror,asappropriatetotheserver
console.There'smoreonhowthisworksintheHowitworks...sectionat
theendofthisrecipe:
consthandlePushTokens=(message)=>{
//Definedinpreviousstep
letchunks=expo.chunkPushNotifications(notifications);
(async()=>{
for(letchunkofchunks){
try{
letreceipts=awaitexpo.sendPushNotificationsAsync(chunk);
console.log(receipts);
}catch(error){
console.error(error);
}
}
})();
}
17. Nowthatwehavethefunctionsdefinedforhandlingpushnotificationsand
messages,let'sexposethosefunctionsbycreatingAPIendpoints.Ifyou're
notfamiliarwithExpress,it'sapowerfulandeasy-to-useframeworkfor
runningawebserverinNode.Youcanquicklygetuptospeedonthe
basicsofroutingwiththebasicroutingdocsathttps://expressjs.com/en/starter
/basic-routing.html.
We'llbeworkingwithJSONdata,sothefirststepwillbeapplyingthe
JSONparsermiddlewarewithacalltoexpress.json():
app.use(express.json());
18. Eventhoughwewon'treallybeusingtherootpath(/)oftheserver,it's
goodpracticetodefineone.We'lljustrespondwithamessagethatthe
serverisrunning:
app.get('/',(req,res)=>{
res.send('PushNotificationServerRunning');
});
19. First,let'simplementtheendpointforsavingapushnotificationtoken.
WhenaPOSTrequestissenttothe/tokenendpoint,we'llpassthetokenvalue
tothesaveTokenfunctionandreturnaresponsestatingthatthetokenwas
received:
app.post('/token',(req,res)=>{
saveToken(req.body.token.value);
console.log(`Receivedpushtoken,${req.body.token.value}`);
res.send(`Receivedpushtoken,${req.body.token.value}`);
});
20. Likewise,the/messageendpointwilltakethemessagefromtherequestbody
andpassittothehandlePushTokensfunctionforprocessing.Then,we'llsend
backaresponsethatthemessagewasreceived:
app.post('/message',(req,res)=>{
handlePushTokens(req.body.message);
console.log(`Receivedmessage,${req.body.message}`);
res.send(`Receivedmessage,${req.body.message}`);
});
21. ThelastpiecetotheserveristhecalltoExpress'slistenmethodonthe
serverinstance,whichwillstarttheserver:
app.listen(PORT_NUMBER,()=>{
console.log('ServerOnlineonPort${PORT_NUMBER}');
});
22. We'regoingtoneedawaytostarttheserver,sowe'lladdacustomscriptto
thepackage.jsonfilecalledserve.Openthepackage.jsonfileandupdateitto
haveascriptsobjectwithanewservescript.Withthisadded,wecanrunthe
serverwithyarnviatheyarnrunservecommandorwithnpmviathefollowing
command:npmrunserve.Thepackage.jsonfileshouldlooksomethinglikethis:
{
"main":"node_modules/expo/AppEntry.js",
"private":true,
"dependencies":{
"esm":"^3.0.28",
"expo":"^27.0.1",
"expo-server-sdk":"^2.3.3",
"express":"^4.16.3",
"react":"16.3.1",
"react-native":"https://github.com/expo/react-native/archive/sdk-27.0.0.tar.gz"
},
"scripts":{
"serve":"node-resmserver/index.js"
}
}
23. We'vegotallthecodeinplace,let'suseit!Asmentionedpreviously,push
notificationpermissionsdonotworkproperlyontheemulator,soareal
devicewillbeneededtotestthepushnotificationfunctionality.First,we'll
fireupournewlycreatedserverbyrunningthefollowingcommands:
yarnrunserve
npmrunserve
YoushouldbegreetedbytheServerOnlinemessagewedefinedin
thelistenmethodcallinstep21:
24. Next,we'llneedtorunngroktoexposeourservertotheinternet.Opena
newTerminalwindowandcreateanngroktunnelwiththefollowing
command:
ngrokhttp3000
YoushouldseethengrokinterfaceintheTerminal.Thisdisplaysthe
URLsgeneratedbyngrok.Inthiscase,ngrokisforwardingmyserver
locatedathttp://localhost:3000totheURLhttp://ddf558bd.ngrok.io.Let's
copythatURL:
25. Youcantestthattheserverisrunningandaccessiblefromtheinternetby
visitingthegeneratedURLinabrowser.NavigatingdirectlytothisURL
behavesexactlythesameasnavigatingtohttp://localhost:3000,whichmeans
theGETendpointwedefinedinpreviousstepshouldrun.Thatfunction
returnsthePushNotificationServerRunningstring,andshoulddisplayin
yourbrowser:
26. Nowthatwe'veconfirmedthattheserverisrunning,let'supdatetheReact
NativeapptousethecorrectserverURL.Instep2,weaddedtoconstants
toholdourAPIendpoints,butwedidn'thavethecorrectURLyet.Let's
updatetheseURLstoreflectthetunnelURLgeneratedbyngrok:
constPUSH_REGISTRATION_ENDPOINT='http://ddf558bd.ngrok.io/token';
constMESSAGE_ENPOINT='http://ddf558bd.ngrok.io/message';
27. Asmentionedpreviously,you'llneedtorunthisapponarealdeviceforthe
permissionsrequesttoworkcorrectly.Assoonasyouopentheapp,you
shouldbepromptedbythedevice,askingifyou'dliketoallowtheappto
sendnotifications:
28. AssoonasAllowisselected,thepushnotificationtokenwillbesenttothe
server's/tokenendpointtobesaved.Thisshouldalsoprinttheassociated
console.logstatementintheserverTerminalwiththesavedtoken.Inthis
case,myiPhone'spushtokenisthestring
ExponentPushToken[g5sIEbOm2yFdzn5VdSSy9n]:
29. Atthispoint,ifyouhaveasecondAndroidoriOSdevice,goaheadand
opentheReactNativeapponthatdeviceaswell.Ifnot,don'tworry.
There'sanothereasywaytotestthatourpushnotificationfunctionalityis
workingwithoutusingaseconddevice.
30. UsetheReactNativeapptosendamessage.Ifyou'vegotaseconddevice
thathasregisteredatokenwiththeserver,itshouldreceiveapush
notificationcorrespondingtothenewlysentmessage.Youshouldalsosee
twonewinstancesofconsole.logintheserver:onethatdisplaysthereceived
message,andanotherthatdisplaysthereceiptsarrayreceivedbackfromthe
Exposervers.Eachreceiptobjectinthearraywillhaveastatusproperty
withthevalue'ok'iftheoperationwassuccessful:

31. Ifyoudon'thaveaseconddevicetoteston,youcanuseExpo'spush
notificationtool,hostedathttps://expo.io/dashboard/notifications.Justcopythe
pushtokenfromtheserverTerminalandpasteitintotheinputlabeledEXPO
PUSHTOKEN(fromyourapp).ToemulateamessagesentfromourReact
Nativeapp,setMESSAGETITLEtoMessagereceived!,MESSAGEBODY
tothemessagetextyou'dliketosend,andcheckthePlaySoundcheckbox.
Ifyoulike,youcanalsoemulatethedataobjectbyprovidingaJSONobject
withakeyof"message"andavalueofyourmessagetext,suchas{"message":
"Thisisatestmessage."}.Thereceivedmessageshouldthenlooksomething
likethisscreenshot:
Howitworks...
Therecipewebuilthereisalittlecontrived,butthecoreconceptsneededto
requestpermissions,registertokens,acceptappdata,andsendpushnotifications
inresponsetoappdataareallthere.
Instep4,wedefinedthefirstpartoftheregisterForPushNotificationsAsyncfunction.
Webeganbyaskingtheuserfortheirpermissiontosendthemnotificationsfrom
ourappviathePermissions.askAsyncmethod,passingintheenumforthepush
notificationspermission,Permissions.NOTIFICATIONS.Wethensavedthestatus
propertyfromtheresolvedreturnobject,whichwillhavethevalue'granted'ifthe
usergrantedpermission.Ifwedon'tgetpermission,wereturnrightaway;
otherwise,wegetthetokenfromExpo'sNotificationscomponentby
callinggetExpoPushTokenAsync.Thisfunctionreturnsatokenstring,whichwillbein
thefollowingformat:
ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]
Instep5,wedefinedthePOSTcalltotheserver'sregistrationendpoint(/token).
Thisfunctionsendsthetokenintherequestbody,whichisthensavedonthe
serverusingthesaveTokenfunctiondefinedinstep14.
Instep6,wecreatedaneventlistenerthatwilllistenforanynewincomingpush
notifications.ThisisdonebycallingNotifications.addListenerandpassingina
callbackfunctiontobeexecutedeverytimeanewnotificationisreceived.On
iOSdevices,thesystemisdesignedtoonlyproduceapushnotificationifthe
appsendingthepushnotificationisn'topenandforegrounded.Thatmeansifyou
trytosendyouruserapushnotificationwhilethey'recurrentlyusingyourapp,
theywillneverreceiveit.
Toovercomethisissue,Exposuggestsmanuallydisplayingthepushnotification
datafromwithinyourapp.ThisNotifications.addListenermethodwascreatedto
fulfillthisneed.Whenapushnotificationisreceived,thecallbackpassed
toaddListenerwillbeexecutedandwillreceivethenewnotificationobjectasa
parameter.Instep7,wesavedthisnotificationtostatesothattheUIwouldbe
re-renderedaccordingly.WeonlydisplayedthemessagetextinaTextcomponent
inthisrecipe,butyoucouldalsouseamodalforamorenotification-like
presentation.
Instep11,wecreatedthesendMessagefunction,whichpoststhemessagetext
storedonstatetotheserver's/messageendpoint.Thiswillexecute
thehandlePushTokenserverfunctiondefinedinstep15.
Instep13,westartedworkingontheserver,whichutilizesExpressandtheExpo
serverSDK.Anewserveriscreatedwithexpressbycallingexpress()directly,as
alocalconst,usuallynamedappbyconvention.Wewereabletocreateanew
ExposerverSDKinstancewithnewExpo(),storingitintheexpoconst.Welater
usedtheExposerverSDKtosendthepushnotificationusingexpo,defineroutes
usingappinsteps17tostep20,andinitiatetheserverbycallingapp.listen()in
step22.
Instep14,wedefinedthesaveTokenfunction,whichwillbeexecutedwhen
the/tokenendpointisusedbytheReactNativeapptoregisteratoken.This
functionsavestheincomingtokentothesavedPushTokensarray,tobeusedlater
whenamessagearrivesfromauser.Inarealapp,thisiswhereyouwouldlikely
wanttosavethetokenstoapersistentdatabaseofsomekind,suchasSQL,
MongoDB,orFirebaseDatabase.
Instep15,westarteddefiningthehandlePushTokensfunction,whichrunswhenthe
ReactNativeappusesthe/messageendpoint.Thefunctionloopsoverthe
savedPushTokensarrayforprocessing.Eachtokenischeckedforvalidityusingthe
ExposerverSDK'sisExpoPushTokenmethod,whichtakesinatokenandreturnstrue
ifthetokenisvalid.Ifit'sinvalid,weloganerrortotheserverconsole.Ifit's
valid,wepushanewnotificationobjectontothelocalnotificationsarrayfor
batchprocessinginthenextstep.Eachnotificationobjectrequiresatoproperty
withthevaluesettoavalidExpopushtoken.Allotherpropertiesareoptional.
Theoptionalpropertieswesetwereasfollows:
Sound:Canbedefaulttoplaythedefaultnotificationsoundornullforno
sound
Title:Thetitleofthepushnotification,usuallydisplayedinbold
Body:Thebodyofthepushnotification
Data:AcustomdataJSONobject
Instep16,weusedtheExposerverSDK'schunkPushNotificationsinstance
methodtocreateanarrayofdatachunksoptimizedforsendingtoExpo'spush
notificationserver.Wethenloopedoverthechunks,andsenteachchunkto
Expo'spushnotificationserverviatheexpo.sendPushNotificationsAsyncmethod.It
returnedapromisethatresolvedtoanarrayofreceiptsforeachpush
notification.Iftheprocessissuccessful,therewillbea{status:'ok'}objectfor
eachnotificationinthearray.
Thisendpoint'sbehaviorissimplerthanarealserverwouldprobablybe,
becausemostmessageapplicationswouldhaveamorecomplicatedwayof
handlingamessage.Attheveryleast,therewouldlikelybealistofrecipients
thatwoulddictatewhichregistereddeviceswouldinturnreceiveaparticular
pushnotification.Thelogicwasintentionallykeptsimpletoportraythebasic
flow.
Instep18,wedefinedthefirstaccessiblerouteonourserver,theroot(/)path.
ExpressprovidesthegetandposthelpermethodsforeasilymakingAPI
endpointsforGETandPOSTrequestsrespectively.Thecallbackfunctionreceivesa
requestobjectandresponseobjectasparameters.AllserverURLsneedto
respondtotherequest;otherwise,therequestwouldtimeout.Theresponseis
sentviathesendmethodontheresponseobject.Thisroutedoesn'tprocessany
data,sowejustreturnedthestringindicatingthatourserverisrunning.
Instep19andstep20,wedefinedPOSTendpointsfor/tokenand/message,which
willexecutesaveTokenandhandlePushTokensrespectively.Wealsoaddedconsole.log
statementstoeach,tologthetokenandthemessagetotheserverTerminalfor
easeofdevelopment.
Instep21,wedefinedthelistenmethodonourExpressserver,whichstartsthe
server.Thefirstparameteristheportnumbertolistenforrequestson,andthe
secondparameterisacallbackfunction,usuallyusedtoconsole.logamessageto
theserverTerminalthattheserverhasbeenstarted.
Instep22,weaddedacustomscripttothepackage.jsonfileofourproject.Any
commandthatcanberunintheTerminalcanbemadeacustomnpmscriptby
addingascriptskeytothepackage.jsonfilesettoanobjectwhosekeysarethe
nameofthecustomscript,andwhosevaluesarethecommandthatshouldbe
executedwhenthatcustomscriptisrun.Inthisrecipe,wedefinedacustom
scriptednamedservethatrunsthenode-resmserver/index.jscommand.This
commandrunsourserverfile(server/index.js)withNode,usingtheesmnpm
packageweinstalledatthebeginningofthisrecipe.Customscriptscanbe
executedwithnpm:
npmrun[custom-script-name]
Theycanalsobeexecutedusingyarn:
yarnrun[custom-script-name]
There'smore...
Pushnotificationscanbecomplicated,butthankfullyExposimplifiesthe
processinanumberofways.There'sgreatdocumentationonExpo'spush
notificationservice,whichcoversthespecificsofnotificationtiming,Expo
serverSDKsinotherlanguages,andhowtoimplementnotificationsover
HTTP/2.Iencourageyoutoreadmoreathttps://docs.expo.io/versions/latest/guides
/push-notifications.
Implementingbrowser-based
authentication
IntheLogginginwithFacebookrecipeinChapter8,WorkingwithApplication
LogicandData,wewillcoverusingtheExpoFacebookcomponenttocreatea
loginworkflowforprovidingourappwiththeuser'sbasicFacebook
accountinformation.ExpoalsoprovidesaGooglecomponent,whichprovides
similarfunctionalityforgettingauser'sGoogleaccountinformation.Butwhat
dowedoifwewanttocreatealoginworkflowthatusesaccountinformation
fromadifferentsite?Inthiscase,ExpoprovidestheAuthSessioncomponent.
AuthSessionisbuiltonExpo'sWebBrowsercomponent,whichwe'vealreadyusedinCh
apter4,ImplementingComplexUserInterfaces–PartII.Thetypicallogin
workflowconsistsoffoursteps:
1. Theuserinitiatestheloginprocess
2. Thewebbrowseropenstotheloginpage
3. Theauthenticationproviderprovidesaredirectonsuccessfullogin
4. TheReactNativeapphandlestheredirect
Inthisapp,we'llbeusingtheSpotifyAPItogetSpotifyaccountinformationfor
ourappviauserlogin.Headovertohttps://beta.developer.spotify.com/dashboard/appl
icationstocreateanewSpotifydevaccount(ifyoudon'talreadyhaveone)anda
newapp.Theappcanbenamedwhateveryoulike.Oncetheappiscreatedwith
Spotify,you'llseeaclientIDstringdisplayedintheinformationforyourapp.
We'llneedthisIDwhenbuildingtheReactNativeapp.
Gettingready
Wewillneedanewappforthisrecipe.Let'snametheappbrowser-based-auth.
TheredirectURIalsoneedstobewhitelistedintheSpotifyappwecreated
previously.Theredirectshouldbeintheform
ofhttps://auth.expo.io/@YOUR_EXPO_USERNAME/YOUR_APP_SLUG.SincemyExpousernameis
warlyware,andsincethisReactNativeappwe'rebuildingisnamedbrowser-based-
auth,myredirectURIishttps://auth.expo.io/@warlyware/browser-based-auth.Besureto
addthistotheRedirectURIslistinthesettingsoftheSpotifyapp.
Howtodoit...
1. We'llstartbyopeningApp.jsandimportingthedependencieswewillbe
using:
importReact,{Component}from'react';
import{TouchableOpacity,StyleSheet,Text,View}from'react-native';
import{AuthSession}from'expo';
import{FontAwesome}from'@expo/vector-icons';
2. Let'salsodeclaretheClient-IDasaconstanttobeusedlater.CopytheClient-
IDfortheSpotifyappwecreatedpreviouslysothatwecansaveitinthe
CLIENT_IDconst:
constCLIENT_ID=Your-Spotify-App-Client-ID;
3. Let'screatetheAppclassandtheinitialstate.TheuserInfopropertywillhold
theuserinformationwereceivebackfromtheSpotifyAPI,anddidErrorisa
Booleanfortrackingwhetheranerroroccurredduringlogin:
exportdefaultclassAppextendsReact.Component{
state={
userInfo:null,
didError:false
};
//Definedinfollowingsteps
}
4. Next,let'sdefinethemethodthatlogstheuserintoSpotify.TheAuthSession
component'sgetRedirectUrlmethodprovidestheredirectURLneededfor
returningtotheReactNativeappafterlogin,whichisthesameredirect
URIwesavedintheSpotifyappintheGettingreadysectionofthisrecipe.
We'llthenusetheredirectURLintheloginrequest,whichwe'lllaunch
withtheAuthSession.startAsyncmethod,passinginanoptionsobjectwiththe
authUrlpropertysettotheSpotifyendpointforauthorizinguserdatawithan
app.There'smoreinformationonthisURLintheHowitworks...sectionat
theendofthisrecipe:
handleSpotifyLogin=async()=>{
letredirectUrl=AuthSession.getRedirectUrl();
letresults=awaitAuthSession.startAsync({
authUrl:
`https://accounts.spotify.com/authorize?client_id=${CLIENT_ID}
&redirect_uri=${encodeURIComponent(redirectUrl)}
&scope=user-read-email&response_type=token`
});
//Definedinnextstep
};
5. WesavedtheresultsofhittingtheSpotifyendpointforuserauthentication
inthelocalresultsvariable.Ifthetypepropertyontheresultsobjectreturns
anythingotherthan'success',thenanerroroccurred,sowe'llupdatethe
didErrorpropertyofstateaccordingly.Otherwise,we'llhitthe/meendpoint
withtheaccesstokenwereceivedfromauthorizationtogettheuser'sinfo,
whichwe'llsavetothis.state.userInfo:
handleSpotifyLogin=async()=>{
if(results.type!=='success'){
this.setState({didError:true});
}else{
constuserInfo=awaitaxios.get(`https://api.spotify.com/v1/me`,{
headers:{
"Authorization":`Bearer${results.params.access_token}`
}
});
this.setState({userInfo:userInfo.data});
}
};
6. Nowthattheauthrelatedmethodsaredefined,let'screatetherender
function.We'llusetheFontAwesomeExpoiconlibrarytodisplaytheSpotify
logo,addabuttontoallowtheusertologin,andaddmethodsforrendering
eitheranerrorortheuserinfo,dependingonthevalueofthis.state.didError.
We'llalsodisabletheloginbuttononcethere'sdatasavedontheuserInfo
propertyofstate:
render(){
return(
<Viewstyle={styles.container}>
<FontAwesome
name="spotify"
color="#2FD566"
size={128}
/>
<TouchableOpacity
style={styles.button}
onPress={this.handleSpotifyLogin}
disabled={this.state.userInfo?true:false}
>
<Textstyle={styles.buttonText}>
LoginwithSpotify
</Text>
</TouchableOpacity>
{this.state.didError?
this.displayError():
this.displayResults()
}
</View>
);
}
7. Next,let'sdefinetheJSXforhandlingerrors.Thetemplatejustdisplaysa
genericerrormessagetoindicatethattheusershouldtryagain:
displayError=()=>{
return(
<Viewstyle={styles.userInfo}>
<Textstyle={styles.errorText}>
Therewasanerror,pleasetryagain.
</Text>
</View>
);
}
8. ThedisplayResultsfunctionwillbeaViewcomponentthatdisplaystheuser's
image,username,andemailaddressifthereisuserInfosavedtostate,
otherwiseitwillprompttheusertologin:
displayResults=()=>{
{returnthis.state.userInfo?(
<Viewstyle={styles.userInfo}>
<Image
style={styles.profileImage}
source={{'uri':this.state.userInfo.images[0].url}}
/>
<View>
<Textstyle={styles.userInfoText}>
Username:
</Text>
<Textstyle={styles.userInfoText}>
{this.state.userInfo.id}
</Text>
<Textstyle={styles.userInfoText}>
Email:
</Text>
<Textstyle={styles.userInfoText}>
{this.state.userInfo.email}
</Text>
</View>
</View>
):(
<Viewstyle={styles.userInfo}>
<Textstyle={styles.userInfoText}>
LogintoSpotifytoseeuserdata.
</Text>
</View>
)}
}
9. Thestylesforthisrecipearequitesimple.Itusesacolumnflexlayout,
appliestheSpotifycolorschemeofblackandgreen,andaddsfontsizesand
margins:
conststyles=StyleSheet.create({
container:{
flexDirection:'column',
backgroundColor:'#000',
flex:1,
alignItems:'center',
justifyContent:'space-evenly',
},
button:{
backgroundColor:'#2FD566',
padding:20
},
buttonText:{
color:'#000',
fontSize:20
},
userInfo:{
height:250,
width:200,
alignItems:'center',
},
userInfoText:{
color:'#fff',
fontSize:18
},
errorText:{
color:'#fff',
fontSize:18
},
profileImage:{
height:64,
width:64,
marginBottom:32
}
});
10. Now,ifwelookattheapp,weshouldbeabletologintoSpotify,andsee
theassociatedimage,username,andemailaddressfortheaccountusedto
login:
Howitworks...
Instep4,wecreatedthemethodforhandlingtheSpotifyloginprocess.The
AuthSession.startAsyncmethodjustneededanauthUrl,whichwasprovidedbythe
SpotifyDevelopersdocumentation.ThefourpiecesrequiredaretheClient-ID,the
redirectURIforhandlingtheresponsefromSpotify,ascopeparameterindicating
thescopeofuserinformationtheappisrequesting,andaresponse_typeparameter
oftoken.Weonlyneedbasicinformationfromtheuser,sowerequestedascope
typeofuser-read-email.Forinformationonallthescopesavailable,checkthe
documentationathttps://beta.developer.spotify.com/documentation/general/guides/scopes
/.
Instep5,wecompletedtheSpotifyloginhandler.Iftheloginwasnot
successful,weupdateddidErroronstateaccordingly.Ifitwassuccessful,weused
thatresponsetoaccesstheSpotifyAPIendpointforgettinguserdata(https://api
.spotify.com/v1/me).WedefinedtheAuthorizationheaderoftheGETrequest
withBearer${results.params.access_token}tovalidatetherequest,asperSpotify's
documentation.Onthesuccessofthisrequest,westoredthereturneduserdata
intheuserInfostateobject,whichre-renderedtheUIanddisplayedtheuser's
information.
ForadeeperdiveintoSpotify'sauthprocess,youcanfindtheguideathttps://bet
a.developer.spotify.com/documentation/general/guides/authorization-guide/.
Seealso
ExpoPermissionsdocs:https://docs.expo.io/versions/latest/sdk/permissions
ExpoMapViewdocs:https://docs.expo.io/versions/latest/sdk/map-view
Airbnb'sReactNativeMapspackage:https://github.com/react-community/react-n
ative-maps
ExpoAudiodocs:https://docs.expo.io/versions/latest/sdk/audio
ReactNativeImagePrefetchdocs:https://facebook.github.io/react-native/docs/
image.html#prefetch
ReactNativeSnapCarouselCustomInterpolationsdocs:https://github.com/a
rchriss/react-native-snap-carousel/blob/master/doc/CUSTOM_INTERPOLATIONS.md
ExpoPushNotificationsdocs:https://docs.expo.io/versions/latest/guides/push-n
otifications
ExpressBasicRoutingguide:https://expressjs.com/en/starter/basic-routing.html
esmpackage:https://github.com/standard-things/esm
ExposerverSDKforNode:https://github.com/expo/exponent-server-sdk-node
ngrokpackage:https://github.com/inconshreveable/ngrok
AddingBasicAnimationstoYour
App
Inthischapter,wewillcoverthefollowingrecipes:
Creatingsimpleanimations
Runningmultipleanimations
Creatinganimatednotifications
Expandingandcollapsingcontainers
Creatingabuttonwithaloadinganimation
Introduction
Inordertoprovideagooduserexperience,we'lllikelywanttoaddsome
animationstodirecttheuser'sattention,tohighlightspecificactions,orjustto
addadistinctivetouchtoourapp.
There'saninitiativeinprogresstomovealltheprocessingfromJavaScripttothe
nativeside.Atthetimeofwriting(ReactNativeVersion0.55),wecanchooseto
usethenativedrivertorunallthesecalculationsinthenativeworld.
Unfortunately,thiscannotbeusedwithallanimations,particularlythoserelated
tolayout,suchasflexboxproperties.Readmoreaboutcaveatswhenusing
nativeanimationinthedocumentationathttp://facebook.github.io/react-native/docs/
animations#caveats.
AlloftherecipesinthischapterusetheJavaScriptimplementation.TheReact
NativeteamhaspromisedtousethesameAPIwhenmovingallofthe
processingtothenativeside,sowedon'tneedtoworryaboutbreakingchanges
totheexistingAPI.
Creatingsimpleanimations
Inthisrecipe,wewilllearnthebasicsofanimations.Wewilluseanimageto
createasimplelinearmovementfromtherighttotheleftofthescreen.
Gettingready
Inordertogothroughthisrecipe,weneedtocreateanemptyapp.Let'scallit
simple-animation.
WearegoingtouseaPNGimageofacloudforthisrecipe.Youcanfindthe
imageintherecipe'srepositoryhostedonGitHubathttps://github.com/warlyware/re
act-native-cookbook/tree/master/chapter-6/simple-animation/assets/images.Placethe
imageinthe/assets/imagesfolderforuseintheapp.
Howtodoit...
1. Let'sbeginbyopeningApp.jsandimportingthedependenciesfortheApp
class.TheAnimatedclasswillberesponsibleforcreatingthevaluesforthe
animation.Itprovidesafewcomponentsthatarereadytobeanimated,and
italsoprovidesseveralmethodsandhelperstorunsmoothanimations.
TheEasingclassprovidesseveralhelpermethodsforbothcalculating
movements(suchaslinearandquadratic)andpredefinedanimations(such
asbounce,ease,andelastic).
WearegoingtousetheDimensionsclasstogetthecurrentdevicesizesothat
weknowwheretoplacetheelementintheinitializationoftheanimation:
importReact,{Component}from'react';
import{
Animated,
Easing,
Dimensions,
StyleSheet,
View,
}from'react-native';
2. We'llalsoinitializesomeconstantsthatwearegoingtoneedinourapp.In
thiscase,wearegoingtogetthedevicedimensions,setthesizeofthe
image,andrequireourimagethatwillbeanimated:
const{width,height}=Dimensions.get('window');
constcloudImage=require('./assets/images/cloud.png');
constimageHeight=200;
constimageWidth=300;
3. Now,let'screatetheAppcomponent.Wearegoingtousetwomethodsfrom
thecomponent'slifecyclesystem.Ifyouarenotfamiliarwiththisconcept,
pleasereviewtherelatedReactdocs(http://reactjs.cn/react/docs/component-spe
cs.html).Thispagealsohasareallynicetutorialonhowlifecyclehooks
work.YoucanalsoreadmoreaboutthisinChapter4,ImplementingComplex
UserInterfaces:PartII,intheDetectingorientationchangesrecipe:
exportdefaultclassAppextendsComponent{
componentWillMount(){
//Definedonstep4
}
componentDidMount(){
//Definedonstep7
}
startAnimation(){
//Definedonstep5
}
render(){
//Definedonstep6
}
}
conststyles=StyleSheet.create({
//Definedonstep8
});
4. Inordertocreateananimation,weneedtodefineastandardvaluetodrive
theanimation.Animated.Valueisaclassthathandlestheanimationvaluesfor
eachframeovertime.Thefirstthingweneedtodoistocreateaninstance
ofthisclasswhenthecomponentiscreated.Inthiscase,weareusingthe
componentWillMountmethod,butwecanalsousetheconstructororeventhe
defaultvaluesofaproperty:
componentWillMount(){
this.animatedValue=newAnimated.Value();
}
5. Oncewehavecreatedtheanimatedvalue,wecandefinetheanimation.We
arealsocreatingaloopbypassingthestartmethodofAnimated.timingan
arrowfunctionthatexecutesthisstartAnimationfunctionagain.Now,when
theimagereachestheendoftheanimation,wewillstartthesame
animationagaintocreateaninfinitelyloopinganimation:
startAnimation(){
this.animatedValue.setValue(width);
Animated.timing(
this.animatedValue,
{
toValue:-imageWidth,
duration:6000,
easing:Easing.linear,
useNativeDriver:true,
}
).start(()=>this.startAnimation());
}
6. Wehaveouranimationinplace,butwearecurrentlyonlycalculatingthe
valuesforeachframeovertime,notdoinganythingwiththosevalues.The
nextstepistorendertheimageonthescreenandsetthepropertyonthe
stylesthatwewanttoanimate.Inthiscase,wewanttomovetheelement
onthex-axis;therefore,weshouldupdatetheleftproperty:
render(){
return(
<Viewstyle={styles.background}>
<Animated.Image
style={[
styles.image,
{left:this.animatedValue},
]}
source={cloudImage}
/>
</View>
);
}
7. Ifwerefreshthesimulator,wewillseetheimageonthescreen,butit'snot
beinganimatedyet.Inordertofixthis,weneedtocallthestartAnimation
method.Wewillstarttheanimationoncethecomponentisfullyrendered,
usingthecomponentDidMountlifecyclehook:
componentDidMount(){
this.startAnimation();
}
8. Ifweruntheappagain,wewillseehowtheimageismovingatthetopof
thescreen,twhatwewant!Asafinalstep,justwhatwewant!Asafinal
step,let'saddsomebasicstylestotheapp:
conststyles=StyleSheet.create({
background:{
flex:1,
backgroundColor:'cyan',
},
image:{
height:imageHeight,
position:'absolute',
top:height/3,
width:imageWidth,
},
});
Theoutputisasshowninfollowingscreenshot:
Howitworks...
Instep5,wesettheanimationvalues.Thefirstlineresetstheinitialvalueevery
timewecallthismethod.Forthisexample,theinitialvaluewillbethewidthof
thedevice,whichwillmovetheimagetotheright-handsideofthescreen,
wherewewanttostartouranimation.
Then,weusetheAnimated.timingfunctiontocreateananimationbasedontime
andtaketwoparameters.Forthefirstparameter,wepassinanimatedValue,which
wecreatedinthecomponentWillMountlifecyclehookinstep4.Thesecondparameter
isanobjectwithconfigurationsfortheanimation.Inthiscase,wearegoingto
settheendvaluetominusthewidthoftheimage,whichwillplacetheimageon
theleft-handsideofthescreen.Wecompletetheanimationthere.
Withtheentireconfigurationinplace,theAnimatedclasswillcalculateallthe
framesrequiredinthe6secondsallottedtoperformalinearanimationfrom
righttoleft(viathedurationpropertybeingsetto6000milliseconds).
WehaveanotherhelperprovidedbyReactNativethatcanbepairedwith
Animated,calledEasing.Inthiscase,weareusingthelinearpropertyoftheEasing
helperclass.Easingprovidesothercommoneasingmethods,such
aselasticandbounce.TakealookattheEasingclassdocumentationandtrysetting
differentvaluesfortheeasingpropertytoseehoweachworks.Youcanfindthe
documentationathttps://facebook.github.io/react-native/docs/easing.html.
Oncetheanimationisconfiguredcorrectly,weneedtorunit.Wedothisby
callingthestartmethod.Thismethodreceivesanoptionalcallbackfunction
parameterthatwillbeexecutedwhentheanimationiscompleted.Inthiscase,
wearerunningthesamestartAnimationfunctionrecursively.Thiswillcreatean
infiniteloop,whichiswhatwewanttoachieve.
Instep6,wearerenderingtheimage.Ifwewanttoanimateanimage,we
shouldalwaysusetheAnimate.Imagecomponent.Internally,thiscomponentwill
handlethevaluesoftheanimationandwillseteachvalueforeveryframeonthe
nativecomponent.ThisavoidsrunningtherendermethodintheJavaScriptlayer
oneveryframe,allowingforsmootheranimations.
AlongwiththeImage,wecanalsoanimatetheView,Text,andScrollView
components.There'ssupportforallfourofthesecomponentsoutofthebox,but
wecouldalsocreateanewcomponentandaddsupportforanimationsvia
Animated.createAnimatedComponent().Allfourofthesecomponentsareabletohandle
stylechanges.AllwehavetodoispassanimatedValuetothepropertythatwewant
toanimate,inthiscasetheleftproperty,butwecoulduseanyoftheavailable
stylesoneachcomponent.
Runningmultipleanimations
Inthisrecipe,wewilllearnhowtousethesameanimationvaluesinseveral
elements.Thisway,wecanreusethesamevalues,alongwithinterpolation,to
getdifferentvaluesfortheremainingelements.
Thisanimationwillbesimilartothepreviousrecipe.Thistime,wewillhave
twoclouds:onewillbesmallerwithslowermovement,theotherlargerand
fastermoving.Atthecenterofthescreen,wewillhaveastaticairplane.We
won'taddanyanimationtotheairplane,butthemovingcloudswillmakeit
appearasthoughtheplaneismoving.
Gettingready
Let'sstartthisrecipebycreatinganemptyappcalledmultiple-animations.
Wearegoingtousethreedifferentimages:twocloudsandanairplane.Youcan
downloadtheimagesfromtherecipe'srepository,hostedonGitHubathttps://git
hub.com/warlyware/react-native-cookbook/tree/master/chapter-6/multiple-animations/assets/i
mages.Makesuretoplacetheimagesinthe/assets/imagesfolder.
Howtodoit...
1. Let'sstartbyopeningApp.jsandaddingourimports:
importReact,{Component}from'react';
import{
View,
Animated,
Image,
Easing,
Dimensions,
StyleSheet,
}from'react-native';
2. Additionally,weneedtodefinesomeconstantsandrequiretheimagesthat
wearegoingtousefortheanimations.Notethatwe'reusingthesame
cloudimageascloudImage1andcloudImage2,butwewilltreatthemasseparate
entitiesinthisrecipe:
const{width,height}=Dimensions.get('window');
constcloudImage1=require('./assets/images/cloud.png');
constcloudImage2=require('./assets/images/cloud.png');
constplaneImage=require('./assets/images/plane.gif');
constcloudHeight=100;
constcloudWidth=150;
constplaneHeight=60;
constplaneWidth=100;
3. Inthenextstep,wearegoingtocreatetheanimatedValueinstancewhenthe
componentgetscreated,thenwewillstarttheanimationwhenthe
componentisfullyrendered.Wearecreatingananimationthatrunsinan
infiniteloop.Theinitialvaluewillbe1andthefinalvaluewillbe0.Ifyou
arenotclearaboutthiscode,makesuretoreadthefirstrecipeinthis
chapter:
exportdefaultclassAppextendsComponent{
componentWillMount(){
this.animatedValue=newAnimated.Value();
}
componentDidMount(){
this.startAnimation();
}
startAnimation(){
this.animatedValue.setValue(1);
Animated.timing(
this.animatedValue,
{
toValue:0,
duration:6000,
easing:Easing.linear,
}
).start(()=>this.startAnimation());
}
render(){
//Definedinalaterstep
}
}
conststyles=StyleSheet.create({
//Definedinalaterstep
});
4. Therendermethodinthisrecipeisgoingtobequitedifferentfromthelast.
Inthisrecipe,wearegoingtoanimatetwoimagesusingthesame
animatedValue.Theanimatedvaluewillreturnvaluesfrom1to0;however,we
wanttomovethecloudsfromrighttoleft,soweneedtosettheleftvalue
oneachelement.
Inordertosetthecorrectvalues,weneedtointerpolateanimatedValue.For
thesmallercloud,wewillsettheinitialleftvaluetothewidthofthe
device,butforthebiggercloud,wewillsettheinitialleftvaluefaraway
fromtheright-handedgeofthedevice.Thiswillmakethemovement
distancebigger,andthereforeitwillmovefaster:
render(){
constleft1=this.animatedValue.interpolate({
inputRange:[0,1],
outputRange:[-cloudWidth,width],
});
constleft2=this.animatedValue.interpolate({
inputRange:[0,1],
outputRange:[-cloudWidth*5,width+cloudWidth*5],
});

//Definedinalaterstep
}
5. Oncewehavethecorrectleftvalues,weneedtodefinetheelementswe
wanttoanimate.Here,wewillsettheinterpolatedvaluetotheleftstyles
property:
render(){
//Definedinalaterstep
return(
<Viewstyle={styles.background}>
<Animated.Image
style={[
styles.cloud1,
{left:left1},
]}
source={cloudImage1}
/>
<Image
style={styles.plane}
source={planeImage}
/>
<Animated.Image
style={[
styles.cloud2,
{left:left2},
]}
source={cloudImage2}
/>
</View>
);
}
6. Asforthelaststep,weneedtodefinesomestyles,justtosetthewidthand
heightofeachcloudaswellasassignthetop:
conststyles=StyleSheet.create({
background:{
flex:1,
backgroundColor:'cyan',
},
cloud1:{
position:'absolute',
width:cloudWidth,
height:cloudHeight,
top:height/3-cloudWidth/2,
},
cloud2:{
position:'absolute',
width:cloudWidth*1.5,
height:cloudHeight*1.5,
top:height/2,
},
plane:{
position:'absolute',
height:planeHeight,
width:planeWidth,
top:height/2-planeHeight,
left:width/2-planeWidth,
}
});
7. Ifwerefreshourapp,weshouldseetheanimation:
Howitworks...
Instep4,wedefinedtheinterpolationstogettheleftvalueforeachcloud.The
interpolatemethodreceivesanobjectwithtworequiredconfigurations,
inputRangeandoutputRange.
TheinputRangeconfigurationreceivesanarrayofvalues.Thesevaluesshould
alwaysbeascendingvalues;youcouldusenegativevaluestoo,aslongasthe
valuesareascending.
outputRangeshouldmatchthenumberofvaluesdefinedoninputRange.Thesearethe
valuesthatweneedasaresultoftheinterpolation.
Forthisrecipe,inputRangegoesfrom0to1,whicharethevaluesofour
animatedValue.InoutputRange,wedefinedthelimitsofthemovementthatweneed.
Creatinganimatednotifications
Inthisrecipe,wewillcreateanotificationcomponentfromscratch.When
showingthenotification,thecomponentwillslideinfromthetopofthescreen.
Afterafewseconds,wewillautomaticallyhideitbyslidingitout.
Gettingready
Wearegoingtocreateanapp.Let'scallitnotification-animation.
Howtodoit...
1. We'llstartbyworkingontheAppcomponent.First,let'simportallthe
requireddependencies:
importReact,{Component}from'react';
import{
Text,
TouchableOpacity,
StyleSheet,
View,
SafeAreaView,
}from'react-native';
importNotificationfrom'./Notification';
2. Oncewehaveallthedependenciesimported,wecandefinetheAppclass.In
thiscase,wearegoingtoinitializethestatewithanotifypropertyequalto
false.Wearegoingtousethispropertytoshoworhidethenotification.By
default,thenotificationwillnotbeshownonscreen.Tomakethingssimple,
wewilldefinethemessagepropertyinthestatewiththetextwewantto
display:
exportdefaultclassAppextendsComponent{
state={
notify:false,
message:'Thisisanotification!',
};
toggleNotification=()=>{
//Definedonlaterstep
}
render(){
//Definedonlaterstep
}
}
conststyles=StyleSheet.create({
//Definedonlaterstep
});
3. Insidetherendermethod,weneedtoshowthenotificationonlyifthenotify
propertyistrue.Wecanachievethisbyusinganifstatement:
render(){
constnotify=this.state.notify
?<Notification
autoHide
message={this.state.message}
onClose={this.toggleNotification}
/>
:null;
//Definedonnextstep
}
4. Inthepreviousstep,weonlydefinedthereferencetotheNotification
component,butwearenotusingityet.Let'sdefineareturnwithallofthe
JSXneededforthisapp.Tokeepthingssimple,weareonlygoingtodefine
atoolbar,sometext,andabuttontotogglethestateofthenotificationwhen
pressed:
render(){
//Codefrompreviousstep
return(
<SafeAreaView>
<Textstyle={styles.toolbar}>Maintoolbar</Text>
<Viewstyle={styles.content}>
<Text>
Loremipsumdolorsitamet,consecteturadipiscing
elit,
seddoeiusmodtemporincididuntutlaboreet
doloremagna.
</Text>
<TouchableOpacity
onPress={this.toggleNotification}
style={styles.btn}
>
<Textstyle={styles.text}>Shownotification</Text>
</TouchableOpacity>
<Text>
Sedutperspiciatisundeomnisistenatuserrorsit
accusantiumdoloremquelaudantium.
</Text>
{notify}
</View>
</SafeAreaView>
);
}
5. Wealsoneedtodefinethemethodthattogglesthenotifypropertyonthe
state,whichisverysimple:
toggleNotification=()=>{
this.setState({
notify:!this.state.notify,
});
}
6. Wearealmostdonewiththisclass.Theonlythingsleftarethestyles.In
thiscase,wewillonlyaddbasicstylessuchascolor,padding,fontSize,
backgroundColor,andmargin,nothingreallyspecial:
conststyles=StyleSheet.create({
toolbar:{
backgroundColor:'#8e44ad',
color:'#fff',
fontSize:22,
padding:20,
textAlign:'center',
},
content:{
padding:10,
overflow:'hidden',
},
btn:{
margin:10,
backgroundColor:'#9b59b6',
borderRadius:3,
padding:10,
},
text:{
textAlign:'center',
color:'#fff',
},
});
7. Ifwetrytoruntheapp,wewillseeanerrorthatthe./Notificationmodule
couldn'tberesolved.Let'sfixthatbydefiningtheNotificationcomponent.
Let'screateaNotificationsfolder,withanindex.jsfileinsideofit.Then,we
canimportourdependencies:
importReact,{Componen}from'react';
import{
Animated,
Easing,
StyleSheet,
Text,
}from'react-native';
8. Oncewehavethedependenciesimported,let'sdefinethepropsandthe
initialstateofournewcomponent.Wearegoingtodefinesomethingvery
simple,justapropertytoreceivethemessagetodisplay,andtwocallback
functionstoallowtherunningofsomeactionswhenthenotification
appearsonthescreenandwhenitgetsclosed.We'llalsoaddapropertyto
setthenumberofmillisecondstodisplaythenotificationbeforeit
autohides:
exportdefaultclassNotificationextendsComponent{
staticdefaultProps={
delay:5000,
onClose:()=>{},
onOpen:()=>{},
};
state={
height:-1000,
};
}
9. It'sfinallytimetoworkontheanimation!Weneedtostarttheanimationas
soonasthecomponentgetsrendered.Ifthere'ssomethingnotclearinthe
followingcode,Irecommendyoutakealookatthefirstandsecondrecipes
inthischapter:
componentWillMount(){
this.animatedValue=newAnimated.Value();
}
componentDidMount(){
this.startSlideIn();
}
getAnimation(value,autoHide){
const{delay}=this.props;
returnAnimated.timing(
this.animatedValue,
{
toValue:value,
duration:500,
easing:Easing.cubic,
delay:autoHide?delay:0,
}
);
}
10. Sofar,we'vedefinedamethodtogettheanimation.Fortheslide-in
movement,weneedtocalculatethevaluesfrom0to1.Oncetheanimation
iscomplete,weneedtoruntheonOpencallback.IftheautoHidepropertyisset
totruewhentheonOpenmethodiscalled,wewillautomaticallyruntheslide-
outanimationtoremovethecomponent:
startSlideIn(){
const{onOpen,autoHide}=this.props;
this.animatedValue.setValue(0);
this.getAnimation(1)
.start(()=>{
onOpen();
if(autoHide){
this.startSlideOut();
}
});
}
11. Similartotheprecedingstep,weneedamethodfortheslide-out
movement.Here,weneedtocalculatethevaluesfrom1to0.Weare
sendingtheautoHidevalueasaparametertothegetAnimationmethod.This
willautomaticallydelaytheanimationbytheamountofmilliseconds
definedbythedelayproperty(inourcase,5seconds).Aftertheanimation
hascompleted,weneedtoruntheonClosecallbackfunction,whichwill
removethecomponentfromtheAppclass:
startSlideOut(){
const{autoHide,onClose}=this.props;
this.animatedValue.setValue(1);
this.getAnimation(0,autoHide)
.start(()=>onClose());
}
12. Finally,let'saddtherendermethod.Here,wewillgetthemessagevalue
providedbyprops.Wealsoneedtheheightofthecomponenttomovethe
componenttotheinitialpositionoftheanimation;bydefault,it's-1000but
wewillsetthecorrectvalueatruntimeinthenextsteps.TheanimatedValue
goesfrom0to1or1to0,dependingonwhetherthenotificationisopening
orclosing;therefore,weneedtointerpolateittogettheactualvalues.The
animationwillgofromminustheheightofthecomponentto0;thiswill
resultinaniceslidein/outanimation:
render(){
const{message}=this.props;
const{height}=this.state;
consttop=this.animatedValue.interpolate({
inputRange:[0,1],
outputRange:[-height,0],
});
//Definedonnextstep
}
}
13. Tokeepthingsassimpleaspossible,wewillreturnanAnimated.Viewwith
sometext.Here,wearesettingthetopstylewiththeinterpolationresult,
meaningwewillanimatethetopstyle.Asmentionedbefore,weneedto
calculatetheheightofthecomponentatruntime.Inordertoachievethat,
weneedtousetheonLayoutpropertyoftheview.Thisfunctionwillbecalled
everytimethelayoutupdatesandwillsendthenewdimensionsofthis
componentasaparameter:
render(){
//Codefrompreviousstep
return(
<Animated.View
onLayout={this.onLayoutChange}
style={[
styles.main,
{top}
]}
>
<Textstyle={styles.text}>{message}</Text>
</Animated.View>
);
}
}
14. TheonLayoutChangemethodwillbeverysimple.Wejustneedtogetthenew
heightandupdatethestate.Thismethodreceivesanevent.Fromthisobject,
wecangrabusefulinformation.Forourpurposes,wewillaccessthedata
atnativeEvent.layoutintheeventobject.Thelayoutobjectcontainsthe
screen'swidthandheight,andthexandypositionsonthescreenwhere
theAnimated.Viewcalledthisfunction:
onLayoutChange=(event)=>{
const{layout:{height}}=event.nativeEvent;
this.setState({height});
}
15. Forthelaststep,wewilladdsomestylestothenotificationcomponent.
Sincewewantthiscomponenttoanimateontopofanythingelse,weneed
tosetthepositiontoabsolute,andsettheleftandrightpropertiesto0.We'll
alsoaddsomecolorandpadding:
conststyles=StyleSheet.create({
main:{
backgroundColor:'rgba(0,0,0,0.7)',
padding:10,
position:'absolute',
left:0,
right:0,
},
text:{
color:'#fff',
},
});
16. Thefinalappshouldlooksomethinglikethefollowingscreenshot:
Howitworks...
Instep3,wedefinedtheNotificationcomponent.Thiscomponentreceivesthree
parameters:aflagtoautomaticallyhidethecomponentafterafewseconds,the
messagethatwewanttodisplay,andacallbackfunctionthatwillbeexecuted
whenthenotificationgetsclosed.
WhentheonClosecallbackgetsexecuted,wewilltogglethenotifypropertyto
removetheNotificationinstanceandclearthememory.
Instep4,wedefinedtheJSXtorenderthecomponentsofourapp.It'simportant
torendertheNotificationcomponentaftertheotherssothatthecomponentwill
appearontopofallothercomponents.
Instep6,wedefinedthestateofourcomponent.ThedefaultPropsobjectsetsthe
defaultvaluesforeachproperty.Thesevalueswillbeappliedifnovalueis
assignedtothegivenproperty.
Wedefinedthedefaultforeachcallbackasanemptyfunction.Thisway,wedon't
havetocheckwhetherthosepropshaveavaluebeforetryingtoexecutethem.
Fortheinitialstate,wedefinedtheheightproperty.Theactualheightvaluewillbe
calculatedatruntimebasedonthecontentreceivedinthemessageproperty.This
meansweneedtoinitiallyrenderthecomponentfarawayfromtheoriginal
position.Sincethere'sashortdelaywhenthelayoutiscalculated,wedon'twant
todisplaythenotificationatallbeforeitmovestothecorrectposition.
Instep9,wecreatedtheanimation.ThegetAnimationmethodreceivestwo
parameters:thedelaytobeappliedandtheautoHideBoolean,whichdetermines
whetherthenotificationautomaticallycloses.Weusedthismethodinstep10
andstep11.
Instep13,wereturnedtheJSXforthiscomponent.TheonLayoutfunctionisvery
usefulforgettingthedimensionsofthecomponentwhenthereareupdatestothe
layout.Forexample,ifthedeviceorientationchanges,thedimensionswill
change,inwhichcasewewouldliketoupdatetheinitialandfinalcoordinates
fortheanimation.
There'smore...
Thecurrentimplementationworksprettywell,butthere'saperformance
problemweshouldaddress.Currently,theonLayoutmethodgetsexecutedon
everyframeoftheanimation,whichmeansweareupdatingthestateonevery
frame,whichleadstothecomponentre-renderingoneveryframe!Weshould
avoidthis,andonlyupdateitoncetogettheactualheight.
Tofixthis,wecouldaddasimplevalidationjusttoupdatethestateifthecurrent
valueisdifferentthantheinitialvalue.Thiswillavoidupdatingthestateon
everyframeandwewon'tforcetherenderoverandoveragain:
onLayoutChange=(event)=>{
const{layout:{height}}=event.nativeEvent;
if(this.state.height===-1000){
this.setState({height});
}
}
Whilethisworksforourpurposes,wecouldalsogofurtherandmakesurethe
heightalsogetsupdatedwhentheorientationchanges.However,we'llstophere,
asthisrecipeisquitelongalready.
Expandingandcollapsingcontainers
Inthisrecipe,wewillcreateacustomcontainerelementwithatitleandcontent.
Whenauserpressesthetitle,thecontentwillcollapseorexpand.Thisrecipe
willallowustoexploretheLayoutAnimationAPI.
Gettingready
Let'sstartbycreatinganewapp.We'llcallitcollapsable-containers.
Oncewehavecreatedtheapp,let'salsocreateaPanelfolderwithanindex.jsfile
initforhousingourPanelcomponent.
Howtodoit...
1. Let'sstartbyfocusingonthePanelcomponent.First,weneedtoimportall
thedependenciesthatwearegoingtouseforthisclass:
importReact,{Component}from'react';
import{
View,
LayoutAnimation,
StyleSheet,
Text,
TouchableOpacity,
}from'react-native';
2. Oncewehavethedependencies,let'sdeclarethedefaultPropsforinitializing
thiscomponent.Inthisrecipe,weonlyneedtoinitializetheexpanded
propertytofalse:
exportdefaultclassPanelextendsComponent{
staticdefaultProps={
expanded:false
};
}
conststyles=StyleSheet.create({
//Definedonlaterstep
});
3. Wearegoingtousetheheightpropertyonthestateobjecttoexpandor
collapsethecontainer.Thefirsttimethiscomponentgetscreated,weneed
tochecktheexpandedpropertyinordertosetthecorrectinitialheight:
state={
height:this.props.expanded?null:0,
};
4. Let'srendertherequiredJSXelementsforthiscomponent.Weneedtoget
theheightvaluefromstateandsetittothecontent'sstyleview.When
pressingthetitleelement,wewillexecutethetogglemethod(definedlater)
tochangetheheightvalueofthestate:
render(){
const{children,style,title}=this.props;
const{height}=this.state;
return(
<Viewstyle={[styles.main,style]}>
<TouchableOpacityonPress={this.toggle}>
<Textstyle={styles.title}>
{title}
</Text>
</TouchableOpacity>
<Viewstyle={{height}}>
{children}
</View>
</View>
);
}
5. Asmentionedbefore,thetogglemethodwillbeexecutedwhenthetitle
elementispressed.Here,wewilltoggletheheightonthestateandcallthe
animationwewanttousewhenupdatingthestylesonthenextrender
cycle:
toggle=()=>{
LayoutAnimation.spring();
this.setState({
height:this.state.height===null?0:null,
})
}
6. Tocompletethiscomponent,let'saddsomesimplestyles.Weneedtoset
theoverflowtohidden,otherwisethecontentwillbeshownwhenthe
componentiscollapsed:
conststyles=StyleSheet.create({
main:{
backgroundColor:'#fff',
borderRadius:3,
overflow:'hidden',
paddingLeft:30,
paddingRight:30,
},
title:{
fontWeight:'bold',
paddingTop:15,
paddingBottom:15,
}
7. OncewehaveourPanelcomponentdefined,let'suseitontheAppclass.
First,weneedtorequireallthedependenciesinApp.js:
importReact,{Component}from'react';
import{
Text,
StyleSheet,
View,
SafeAreaView,
Platform,
UIManager
}from'react-native';
importPanelfrom'./Panel';
8. Inthepreviousstep,weimportedthePanelcomponent.Wearegoingto
declarethreeinstancesofthisclassintheJSX:
exportdefaultclassAppextendsComponent{
render(){
return(
<SafeAreaViewstyle={[styles.main]}>
<Textstyle={styles.toolbar}>Animatedcontainers</Text>
<Viewstyle={styles.content}>
<Panel
title={'Container1'}
style={styles.panel}
>
<Textstyle={styles.panelText}>
Temporibusautemquibusdametautofficiis
debitisautrerumnecessitatibussaepe
evenietutetvoluptatesrepudiandaesintet
molestiaenonrecusandae.
</Text>
</Panel>
<Panel
title={'Container2'}
style={styles.panel}
>
<Textstyle={styles.panelText}>
Etharumquidemrerumfacilisestetexpedita
distinctio.Namliberotempore,
cumsolutanobisesteligendioptiocumque.
</Text>
</Panel>
<Panel
expanded
title={'Container3'}
style={styles.panel}
>
<Textstyle={styles.panelText}>
Nullamlobortiseuloremutvulputate.
</Text>
<Textstyle={styles.panelText}>
Donecidelementumorci.Donecfringillalobortis
ipsum,vitaecommodourna.
</Text>
</Panel>
</View>
</SafeAreaView>
);
}
}
9. WeareusingtheReactNativeLayoutAnimationAPIinthisrecipe.ThisAPIis
disabledonAndroidbydefault.InthecurrentversionofReactNative,
beforetheAppcomponentmounts,we'llusethePlatformhelperwiththe
UIManagertoenablethisfeatureonAndroiddevices:
componentWillMount(){
if(Platform.OS==='android'){
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
10. Finally,let'saddsomestylestothetoolbarandthemaincontainer.Wejust
needsomesimplestylesyou'relikelyusedtobynow:padding,margin,and
color:
conststyles=StyleSheet.create({
main:{
flex:1,
},
toolbar:{
backgroundColor:'#3498db',
color:'#fff',
fontSize:22,
padding:20,
textAlign:'center',
},
content:{
padding:10,
backgroundColor:'#ecf0f1',
flex:1,
},
panel:{
marginBottom:10,
},
panelText:{
paddingBottom:15,
}
});
11. Thefinalappshouldlooksimilartothefollowingscreenshots:
Howitworks...
Instep3,wesettheinitialheightofthecontent.Iftheexpandedpropertywassetto
true,thenweshouldshowthecontent.Bysettingtheheightvaluetonull,the
layoutsystemwillcalculatetheheightbasedonthecontent;otherwise,weneed
tosetthevalueto0,whichwillhidethecontentwhenthecomponentis
collapsed.
Instep4,wedefinedalltheJSXforthePanelcomponent.Thereareafew
conceptsinthisstepworthcovering.First,thechildrenpropertyispassedinfrom
thepropsobject,whichwillcontainanyelementsdefinedbetween<Panel>and
</Panel>whenthiscomponentisusedintheAppclass.Thisisveryhelpful
because,byusingthisproperty,weareallowingthiscomponenttoreceiveany
othercomponentsaschildren.
Inthissamestep,we'realsogettingtheheightfromthestateobjectandsettingit
asthestyleappliedtotheViewwiththecollapsiblecontent.Thiswillupdatethe
height,causingthecomponenttocorrespondinglyexpandorcollapse.Wealso
declaredtheonPresscallback,whichtogglestheheightonthestatewhen
thetitleelementispressed.
Instep7,wedefinedthetogglemethod,whichtogglestheheightvalue.Here,we
usedtheLayoutAnimationclass.Bycallingthespringmethod,thelayoutsystemwill
animateeverychangethathappenstothelayoutonthenextrender.Inthiscase,
weareonlychangingheight,butwecanchangeanyotherpropertywewant,such
asopacity,position,orcolor.
TheLayoutAnimationclasscontainsacoupleofpredefinedanimations.Inthis
recipe,weusedspring,butwecouldalsouselinearoreaseInEaseOut,oryoucould
createyourownusingtheconfigureNextmethod.
IfweremovetheLayoutAnimation,wewon'tseeananimation;thecomponentwill
expandandcollapsebyjumpingfrom0tototalheight.Butbyaddingthatsingle
line,we'reabletoeasilyaddanice,smoothanimation.Ifyouneedmorecontrol
overtheanimation,you'llprobablywanttousetheAnimationAPIinstead.
Instep9,wecheckedtheOSpropertyonthePlatformhelper,whichreturnedthe
'android'or'ios'strings,dependingonwhichdevicetheappisrunningon.Ifthe
appisrunningonAndriod,weusetheUIManagerhelper's
setLayoutAnimationEnabledExperimentalmethodtoenabletheLayoutAnimationAPI.
Seealso
LayoutAnimationAPIdocumentationathttps://facebook.github.io/react-native/docs
/layoutanimation.html
AquickintrotoReact'sprops.childrenathttps://codeburst.io/a-quick-intro-to-re
acts-props-children-cb3d2fce4891
Creatingabuttonwithaloading
animation
Inthisrecipe,we'llcontinueworkingwiththeLayoutAnimationclass.Here,wewill
createabutton,andwhentheuserpressesthebutton,wewillshowaloading
indicatorandanimatethestyles.
Gettingready
Togetstarted,we'llneedtocreateanemptyapp.Let'scallitbutton-loading-
animation.
Let'salsocreateaButtonfolderwithanindex.jsfileinitforourButtoncomponent.
Howtodoit...
1. Let'sstartwiththeButton/index.jsfile.First,we'llimportallthe
dependenciesforthiscomponent:
importReact,{Component}from'react';
import{
ActivityIndicator,
LayoutAnimation,
StyleSheet,
Text,
TouchableOpacity,
View,
}from'react-native';
2. We'regoingtouseonlyfourpropsforthiscomponent:alabel,aloading
Booleantotoggledisplayingeithertheloadingindicatororthelabelinside
thebutton,acallbackfunctiontobeexecutedwhenthebuttonispressed,
andcustomstyles.Here,we'llinitthedefaultPropsforloadingtofalse,and
the
handleButtonPresstoanemptyfunction:
exportdefaultclassButtonextendsComponent{
staticdefaultProps={
loading:false,
onPress:()=>{},
};
//Definedonlatersteps
}
3. We'llkeeptherendermethodofthiscomponentassimpleaspossible.We'll
renderthelabelandtheactivityindicatorbasedonthevalueoftheloading
property:
render(){
const{loading,style}=this.props;
return(
<TouchableOpacity
style={[
styles.main,
style,
loading?styles.loading:null,
]}
activeOpacity={0.6}
onPress={this.handleButtonPress}
>
<View>
{this.renderLabel()}
{this.renderActivityIndicator()}
</View>
</TouchableOpacity>
);
}
4. Inordertorenderthelabel,weneedtocheckwhethertheloadingpropertyis
false.Ifitis,thenwereturnonlyaTextelementwiththelabelwereceived
fromprops:
renderLabel(){
const{label,loading}=this.props;
if(!loading){
return(
<Textstyle={styles.label}>{label}</Text>
);
}
}
5. Likewise,therenderActivityIndicatorindicatorshouldonlyapplyifthevalue
oftheloadingpropertyistrue.Ifso,wewillreturntheActivityIndicator
component.We'llusethepropsofActivityIndicatortodefineasizeofsmall
andacolorofwhite(#fff):
renderActivityIndicator(){
if(this.props.loading){
return(
<ActivityIndicatorsize="small"color="#fff"/>
);
}
}
6. Onemethodisstillmissingfromourclass:handleButtonPress.Weneedto
informtheparentofthiscomponentwhenthebuttonhasbeenpressed,
whichcanbedonebycallingtheonPresscallbackpassedtothiscomponent
viaprops.We'llalsousetheLayoutAnimationtoqueueananimationonthenext
render:
handleButtonPress=()=>{
const{loading,onPress}=this.props;
LayoutAnimation.easeInEaseOut();
onPress(!loading);
}
7. Tocompletethiscomponent,weneedtoaddsomestyles.We'lldefinesome
colors,roundedcorners,alignment,padding,andsoon.Fortheloading
styles,whichwillbeappliedwhentheloadingindicatorisdisplayed,we'll
updatethepaddingtocreateacirclearoundtheloadingindicator:
conststyles=StyleSheet.create({
main:{
backgroundColor:'#e67e22',
borderRadius:20,
padding:10,
paddingLeft:50,
paddingRight:50,
},
label:{
color:'#fff',
fontWeight:'bold',
textAlign:'center',
backgroundColor:'transparent',
},
loading:{
padding:10,
paddingLeft:10,
paddingRight:10,
},
});
8. WearedonewiththeButtoncomponent.Now,lets'sworkontheAppclass.
Let'sstartbyimportingallthedependencies:
importReact,{Component}from'react';
import{
Text,
StyleSheet,
View,
SafeAreaView,
Platform,
UIManager
}from'react-native';
importButtonfrom'./Button';
9. TheAppclassisrelativelysimple.Wewillonlyneedtodefinealoading
propertyonthestateobject,whichwilltoggletheButton'sanimation.We'll
alsorenderatoolbarandaButton:
exportdefaultclassAppextendsComponent{
state={
loading:false,
};
//Definedonnextstep
handleButtonPress=(loading)=>{
this.setState({loading});
}
render(){
const{loading}=this.state;
return(
<SafeAreaViewstyle={[styles.main,android]}>
<Textstyle={styles.toolbar}>Animatedcontainers</Text>
<Viewstyle={styles.content}>
<Button
label="Login"
loading={loading}
onPress={this.handleButtonPress}
/>
</View>
</SafeAreaView>
);
}
}
10. Asinthelastrecipe,we'llneedtomanuallyenabletheLayoutAnimationAPI
onAndroiddevices:
componentWillMount(){
if(Platform.OS==='android'){
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
11. Finally,we'lladdsomestyles,justsomecolors,padding,andalignmentfor
centeringthebuttononthescreen:
conststyles=StyleSheet.create({
main:{
flex:1,
},
toolbar:{
backgroundColor:'#f39c12',
color:'#fff',
fontSize:22,
padding:20,
textAlign:'center',
},
content:{
padding:10,
backgroundColor:'#ecf0f1',
flex:1,
alignItems:'center',
justifyContent:'center',
},
});
12. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Instep3,weaddedtherendermethodfortheButtoncomponent.Here,we
receivedtheloadingpropertyand,basedonthatvalue,weappliedthe
correspondingstylestotheTouchableOpacitybuttonelement.Wealsousedtwo
methods:oneforrenderingthelabelandtheotherforrenderingtheactivity
indicator.
Instep6,weexecutedtheonPresscallback.Bydefault,wedeclaredanempty
function,sowedon'thavetocheckwhetherthevalueispresentornot.
Theparentofthisbuttonshouldberesponsibleforupdatingtheloadingproperty
whentheonPresscallbackiscalled.Fromthiscomponent,weareonly
responsibleforinformingtheparentwhenthisbuttonhasbeenpressed.
TheLayoutAnimation.eadeInEaseOutmethodonlyqueuesananimationforthenext
renderphase,whichmeanstheanimationisn'texecutedrightaway.Weare
responsibleforchangingthestylesthatwewanttoanimate.Ifwedon'tchange
anystyles,thenwewon'tseeanyanimations.
TheButtoncomponentdoesn'tknowhowtheloadingpropertygetsupdated.It
mightbebecauseofafetchrequest,atimeout,oranyotheraction.Theparent
componentisresponsibleforupdatingtheloadingproperty.Wheneverany
changeshappen,weapplythenewstylestothebuttonandasmoothanimation
willoccur.
Instep9,wedefinedthecontentoftheAppclass.Here,wemakeuseofourButton
component.Whenthebuttonispressed,thestateoftheloadingpropertyis
updated,whichwillcausetheanimationtoruneverytimethebuttonispressed.
Conclusion
Inthischapter,we'vecoveredthefundamentalsofanimatingyourReactNative
app.Theserecipeshavebeenaimedatbothprovidingusefulpracticalcode
solutions,andalsoestablishinghowtousethebasicbuildingblockssothatyou
arebetterequippedtocreateanimationsthatfityourapp.Hopefully,bynow,
youshouldbegettingcomfortablewiththeAnimatedandLayoutAnimationanimation
helpers.InChapter7,AddingAdvancedAnimationstoYourApp,wewillcombine
thethingswe'velearnedheretobuildoutmorecomplexandinterestingapp-
centricUIanimations.
AddingAdvancedAnimationsto
YourApp
Inthischapter,we'llcoverthefollowingrecipes:
Removingitemsfromalistcomponent
CreatingaFacebookreactionswidget
Displayingimagesinfullscreen
Introduction
Inthepreviouschapter,wecoveredthebasicsofusingthetwomainanimation
helpersinReactNative:AnimatedandLayoutAnimation.Inthischapter,we'lltake
theseconceptsfurtherbybuildingoutmorecomplicatedrecipesthatexhibit
commonnativeUXpatterns.
Removingitemsfromalist
component
Inthisrecipe,we'lllearnhowtocreatelistitemsinaListViewwithananimated
sidewaysslide.Iftheuserslidestheitempastathreshold,theitemisremoved.
Thisisacommonpatterninmanymobileappswitheditablelists.Wearealso
goingtoseehowtousePanRespondertohandledragevents.
Gettingready
Weneedtocreateanemptyapp.Forthisrecipe,we'llnameitremoving-list-items.
WealsoneedtocreateanewContactListfolderandtwofilesinside
it:index.jsandContactItem.js.
Howtodoit...
1. Let'sstartbyimportingthedependenciesforthemainAppclass,asfollows:
importReactfrom'react';
import{
Text,
StyleSheet,
SafeAreaView,
}from'react-native';
importContactListfrom'./ContactList';
2. Thiscomponentwillbesimple.Allweneedtorenderisatoolbarand
theContactListcomponentthatweimportedinthepreviousstep,asfollows:
constApp=()=>(
<SafeAreaViewstyle={styles.main}>
<Textstyle={styles.toolbar}>Contacts</Text>
<ContactListstyle={styles.content}/>
</SafeAreaView>
);
conststyles=StyleSheet.create({
main:{
flex:1,
},
toolbar:{
backgroundColor:'#2c3e50',
color:'#fff',
fontSize:22,
padding:20,
textAlign:'center',
},
content:{
padding:10,
flex:1,
},
});
exportdefaultApp;
3. Thisisallweneedinordertostartworkingontheactuallist.Let'sopenthe
fileatContactList/index.jsandimportallofthedependencies,asfollows:
importReact,{Component}from'react';
import{
ListView,
ScrollView,
}from'react-native';
importContactItemfrom'./ContactItem';
4. Wethenneedtodefinesomedata.Inareal-worldapp,wewouldfetchthe
datafromanAPI,buttokeepthingssimpleandfocusedonlyonthedrag
functionality,let'sjustdefinethedatainthissamefile:
constdata=[
{id:1,name:'JonSnow'},
{id:2,name:'LukeSkywalker'},
{id:3,name:'BilboBaggins'},
{id:4,name:'BobLabla'},
{id:5,name:'Mr.Magoo'},
];
5. Thestateforthiscomponentwillonlycontaintwoproperties:thedatafor
thelistandaBooleanvaluethatwillbeupdatedwhenthedraggingstartsor
ends.IfyouarenotfamiliarwithhowListViewworks,checkoutthe
DisplayingalistofitemsrecipeinChapter2,CreatingaSimpleReactNative
App.Let'sdefinethedataasfollows:
exportdefaultclassContactListextendsComponent{
ds=newListView.DataSource({
rowHasChanged:(r1,r2)=>r1!==r2
});
state={
dataSource:this.ds.cloneWithRows(data),
swiping:false,
};
//Definedinlatersteps
}
6. Therendermethodonlyneedstodisplaythelist.In
therenderScrollComponentproperty,we'llenablescrollingonlywhentheuseris
notswipinganitemonthelist.Iftheuserisswiping,wewanttodisable
verticalscrolling,asfollows:
render(){
const{dataSource,swiping}=this.state;
return(
<ListView
key={data}
enableEmptySections
dataSource={dataSource}
renderScrollComponent={
(props)=><ScrollView{...props}scrollEnabled={!swiping}/>
}
renderRow={this.renderItem}
/>
);
}
7. TherenderItemmethodwillreturneachiteminthelist.Here,weneedto
sendthecontactinformationasaproperty,alongwiththreecallbacks:
renderItem=(contact)=>(
<ContactItem
contact={contact}
onRemove={this.handleRemoveContact}
onDragEnd={this.handleToggleSwipe}
onDragStart={this.handleToggleSwipe}
/>
);
8. Weneedtotogglethevalueoftheswipingpropertyonthestateobject,
whichwilltogglewhetherverticalscrollonthelistislockedornot:
handleToggleSwipe=()=>{
this.setState({swiping:!this.state.swiping});
}
9. Whenremovinganitem,weneedtofindtheindexofthegivencontactand
thenremoveitfromtheoriginallist.Afterthat,weneedto
updatedatasourceonthestatetore-renderthelistwiththeresultingdata:
handleRemoveContact=(contact)=>{
constindex=data.findIndex(
(item)=>item.id===contact.id
);
data.splice(index,1);
this.setState({
dataSource:this.ds.cloneWithRows(data),
});
}
10. Wearedonewiththelist,sonowlet'sfocusonthelistitems.Let'sopen
theContactList/ContactItem.jsfileandimportthedependencieswe'llneed:
importReact,{Component}from'react';
import{
Animated,
Easing,
PanResponder,
StyleSheet,
Text,
TouchableHighlight,
View,
}from'react-native';
11. WeneedtodefinedefaultPropsforthiscomponent.ThedefaultPropsobject
willneedanemptyfunctionforeachofthefourpropsbeingpassedintoit
fromtheparentListViewelement.TheonPressfunctionwillexecutewhenthe
itemispressed,theonRemovefunctionwillexecutewhenthecontactgets
removed,andtwodragfunctionswilllistenfordragevents.Onstate,we
onlyneedtodefineananimatedvaluetoholdthexandycoordinatesofthe
dragging,asfollows:
exportdefaultclassContactItemextendsComponent{
staticdefaultProps={
onPress:()=>{},
onRemove:()=>{},
onDragEnd:()=>{},
onDragStart:()=>{},
};
state={
pan:newAnimated.ValueXY(),
};
12. Whenthecomponentiscreated,weneedtoconfigurePanResponder.Wewill
dothisinthecomponentWillMountlifecyclehook.PanResponderisresponsiblefor
handlinggestures.ItprovidesasimpleAPItocapturetheeventsgenerated
bytheuser'sfinger,asfollows:
componentWillMount(){
this.panResponder=PanResponder.create({
onMoveShouldSetPanResponderCapture:this.handleShouldDrag,
onPanResponderMove:Animated.event(
[null,{dx:this.state.pan.x}]
),
onPanResponderRelease:this.handleReleaseItem,
onPanResponderTerminate:this.handleReleaseItem,
});
}
13. Nowlet'sdefinetheactualfunctionsthatwillgetexecutedforeachcallback
definedinthepreviousstep.WecanstartwiththehandleShouldDragmethod,
asfollows:
handleShouldDrag=(e,gesture)=>{
const{dx}=gesture;
returnMath.abs(dx)>2;
}
14. handleReleaseItemisalittlebitmorecomplicated.Wearegoingtosplitthis
methodintotwosteps.First,weneedtofigureoutwhetherthecurrentitem
needstoberemovedornot.Inordertodothat,weneedtosetathreshold.
Iftheuserslidestheelementbeyondourthreshold,we'llremovetheitem,
asfollows:
handleReleaseItem=(e,gesture)=>{
const{onRemove,contact,onDragEnd}=this.props;
constmove=this.rowWidth-Math.abs(gesture.dx);
letremove=false;
letconfig={//Animationtooriginposition
toValue:{x:0,y:0},
duration:500,
};
if(move<this.threshold){
remove=true;
if(gesture.dx>0){
config={//Animationtotheright
toValue:{x:this.rowWidth,y:0},
duration:100,
};
}else{
config={//Animationtotheleft
toValue:{x:-this.rowWidth,y:0},
duration:100,
};
}
}
//Remainderinnextstep
}
15. Oncewehavetheconfigurationsfortheanimation,wearereadytomove
theitem!First,we'llexecutetheonDragEndcallbackand,iftheitemshouldbe
removed,we'llruntheonRemovefunction,asfollows:
handleReleaseItem=(e,gesture)=>{
//Codefrompreviousstep
onDragEnd();
Animated.spring(
this.state.pan,
config,
).start(()=>{
if(remove){
onRemove(contact);
}
});
}
16. Wehavethefulldraggingsysteminplace.Nowweneedtodefine
therendermethod.Wejustneedtodisplaythecontactnamewithin
theTouchableHighlightelement,wrappedinsideanAnimated.View,asfollows:
render(){
const{contact,onPress}=this.props;
return(
<Viewstyle={styles.row}onLayout={this.setThreshold}>
<Animated.View
style={[styles.pan,this.state.pan.getLayout()]}
{...this.panResponder.panHandlers}
>
<TouchableHighlight
style={styles.info}
onPress={()=>onPress(contact)}
underlayColor="#ecf0f1"
>
<Text>{contact.name}</Text>
</TouchableHighlight>
</Animated.View>
</View>
);
}
17. Weneedonemoremethodonthisclass,whichisfiredonlayoutchangevia
theViewelement'sonLayoutprop.setThresholdwillgetthe
currentwidthofrowandsetthreshold.Inthiscase,we'resettingittobeathird
ofthewidthofthescreen.Thesevaluesarerequiredtodecidewhetherto
removetheitemornot,asfollows:
setThreshold=(event)=>{
const{layout:{width}}=event.nativeEvent;
this.threshold=width/3;
this.rowWidth=width;
}
18. Finally,we'lladdsomestylestotherows,asfollows:
conststyles=StyleSheet.create({
row:{
backgroundColor:'#ecf0f1',
borderBottomWidth:1,
borderColor:'#ecf0f1',
flexDirection:'row',
},
pan:{
flex:1,
},
info:{
backgroundColor:'#fff',
paddingBottom:20,
paddingLeft:10,
paddingTop:20,
},
});
19. Thefinalappshouldlooksomethinglikethisscreenshot: